﻿/* 
* Copyright (c) 2010, The University of Texas at Austin
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, 
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, 
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, 
* this list of conditions and the following disclaimer in the documentation and/or other materials 
* provided with the distribution.
*
* Neither the name of The University of Texas at Austin nor the names of its contributors may be 
* used to endorse or promote products derived from this software without specific prior written 
* permission.
* 
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Media;
using Bio.Data;
using Bio.Data.Interfaces;
using Bio.Views.Structure.Editor.Internal.Data;
using Bio.Views.Structure.Editor.Internal.Editing;
using Bio.Views.Structure.Editor.Internal.Rendering.Interfaces;
using Bio.Views.Structure.Editor.Internal.Rendering.Primitives;
using Bio.Views.Structure.Editor.Internal.Rendering.Visuals;
using Bio.Views.Structure.Editor.Internal.Selection;
using Bio.Views.Structure.Editor.Internal.Selection.Interfaces;
using Bio.Views.Structure.Editor.ViewModels;

namespace Bio.Views.Structure.Editor.Internal.Rendering
{
    public class StructureDrawingManager
    {
        public Rect DrawingBounds { get; private set; }
        //public bool EnforceBounds { get; set; }
        public Rect CanvasBounds { get; private set; }
        public Point CanvasPadding { get; set; }

        public VisualBrush SnapshotBrush { get; set; }        
        public Dictionary<StructureElementSelectionMode, List<IStructureElementSelectionGroup>> ElementSelectionGroups { get; private set; }
        public RangeStructureElementVisualComponent FullModelRangeSelectionVisual { get; private set; }
        public int CurrentBasePairCount { get { return BasePairVisualsByFivePrimeIndex.Values.Count(); } }
        public int CurrentHelixCount { get { return HelixVisualsByFivePrimeStartIndex.Values.Count(); } }
        public int VisibleNucleotideCount { get { return NucleotideVisualsBySequenceIndex.Values.Where(ntVisual => ntVisual.IsComponentVisible).Count(); } }
        public bool DirtyStatus { get { return (EditStack.Count > 0) ? true : false; } }

        public event EventHandler DrawingBoundsChangedEvent;
        public event EventHandler StructurePropertiesChangedEvent;
        public event EventHandler DirtyStateChangedEvent;

        public void SetStructureDrawingData(SecondaryStructureEditorViewModel structureDrawingViewModel)
        {
            if (structureDrawingViewModel != null)
            {
                ResetManager(); //In the future, we will need to check if the data is dirty and save if necessary, etc....
            }
            StructureDrawingViewModel = structureDrawingViewModel;
            SetInitialSequenceRendering();
            CreateNucleotideVisuals();
            CreateBasePairVisuals();
            CreateHelixVisuals();
            CreateLoopVisuals();
            RenderComplete();
            FireDrawingDirtyStateChanged();
        }

        public void SaveStructureElementDrawingOperation(IEnumerable<int> nucleotideIndices, Point delta)
        {
            if (nucleotideIndices == null)
                return;

            if (nucleotideIndices.Count() <= 0)
                return;

            StructureModelEdit edit = new StructureModelEdit()
            {
                EditCategory = StructureModelEdit.EditType.Drawing,
                VisualDrawingEdits = new List<DrawingEdit>()
            };

            DrawingEdit drawingOperation = new DrawingEdit()
            {
                MoveDelta = delta,
                NucleotideVisualsRepositionedByDelta = new List<NucleotideStructureElementVisualComponent>()
            };

            foreach (var editedNtIdx in nucleotideIndices)
            {
                if (NucleotideVisualsBySequenceIndex.ContainsKey(editedNtIdx))
                    drawingOperation.NucleotideVisualsRepositionedByDelta.Add(NucleotideVisualsBySequenceIndex[editedNtIdx]);
            }

            edit.VisualDrawingEdits.Add(drawingOperation);
            EditStack.Push(edit);
            FireDrawingDirtyStateChanged();
            CheckAndUpdateRenderingTopLeft(false); //Slow
            FireDrawingBoundsChangedUpdate();
        }

        public void SaveStructureElementDrawingOperation(Dictionary<int, Point> nucleotideDeltas)
        {
            if (nucleotideDeltas == null)
                return;

            if (nucleotideDeltas.Count <= 0)
                return;

            StructureModelEdit edit = new StructureModelEdit()
            {
                EditCategory = StructureModelEdit.EditType.Drawing,
                VisualDrawingEdits = new List<DrawingEdit>()
            };

            foreach (var ntIdx in nucleotideDeltas.Keys)
            {
                if(NucleotideVisualsBySequenceIndex.ContainsKey(ntIdx))
                {
                    edit.VisualDrawingEdits.Add(
                        new DrawingEdit()
                        {
                            MoveDelta = nucleotideDeltas[ntIdx],
                            NucleotideVisualsRepositionedByDelta = new List<NucleotideStructureElementVisualComponent>() { NucleotideVisualsBySequenceIndex[ntIdx] }
                        });
                }
            }
            EditStack.Push(edit);
            FireDrawingDirtyStateChanged();
            CheckAndUpdateRenderingTopLeft(false); //Slow
            FireDrawingBoundsChangedUpdate();
        }

        public bool RemoveHelix(int helixId)
        {
            if (!HelixVisualsByFivePrimeStartIndex.ContainsKey(helixId))
                return false;

            HelixStructureElementVisualComponent helixToRemove = HelixVisualsByFivePrimeStartIndex[helixId];
            return RemoveBasePairs(Enumerable.Range(helixToRemove.FivePrimeStartNucleotideIndex, (helixToRemove.FivePrimeEndNucleotideIndex - helixToRemove.FivePrimeStartNucleotideIndex + 1)));
        }

        public bool RemoveBasePairs(IEnumerable<int> basePairIDs)
        {
            if (StructureDrawingViewModel.CurrentStructure.Entity.RemoveBasePairs(basePairIDs))
            {
                DisconnectExtentsForModelEdits();

                //Were going to try to identify and preserve any existing visuals that correspond to currently valid base pairs
                var unmodifiedBasePairVisuals = from bp in StructureDrawingViewModel.CurrentStructure.Entity.BasePairs
                                                from bpVisual in BasePairVisualsByFivePrimeIndex.Values
                                                where bp.FivePrimeIndex == bpVisual.Data.FivePrimeIndex &&
                                                bp.ThreePrimeIndex == bpVisual.Data.ThreePrimeIndex
                                                select bpVisual;
                Dictionary<int, BasePairStructureElementVisualComponent> localunmodifiedBasePairsByFivePrimeIndex = new Dictionary<int,BasePairStructureElementVisualComponent>();
                foreach(var unmodifiedBasePairVisual in unmodifiedBasePairVisuals)
                    localunmodifiedBasePairsByFivePrimeIndex.Add(unmodifiedBasePairVisual.Data.FivePrimeIndex, unmodifiedBasePairVisual);

                List<BasePairStructureElementVisualComponent> obseleteBpVisuals = BasePairVisualsByFivePrimeIndex.Values.Except(unmodifiedBasePairVisuals).ToList<BasePairStructureElementVisualComponent>();
                List<IStructureElementSelectionGroup> bpSelectionGroups = ElementSelectionGroups[StructureElementSelectionMode.BasePair];
                var obseleteBpSelGroups = (from bpVisual in obseleteBpVisuals
                                           from selgroup in bpSelectionGroups
                                           where bpVisual == selgroup.Visual
                                           select selgroup).ToList();
                foreach (var obseleteBpSelGroup in obseleteBpSelGroups)
                    bpSelectionGroups.Remove(obseleteBpSelGroup);

                if(EditStack.Count > 0)
                {
                    List<StructureModelEdit> editStackCopy = new List<StructureModelEdit>();
                    while (EditStack.Count > 0)
                        editStackCopy.Insert(0, EditStack.Pop());
                        
                    foreach(var obseleteBpVisual in obseleteBpVisuals)
                    {
                        foreach (var edit in editStackCopy)
                        {
                            if (edit.EditCategory == StructureModelEdit.EditType.Properties &&
                                edit.VisualsWithPropertyEdits != null &&
                                edit.VisualsWithPropertyEdits.Contains(obseleteBpVisual))
                                edit.VisualsWithPropertyEdits.Remove(obseleteBpVisual);
                        }
                    }

                    //Put back any edits that still have state.
                    foreach (var edit in editStackCopy)
                    {
                        if (edit.EditCategory == StructureModelEdit.EditType.Properties)
                        {
                            if (edit.VisualsWithPropertyEdits.Count > 0)
                                EditStack.Push(edit);
                        }
                        else
                            EditStack.Push(edit);
                    }
                }

                foreach (var bpVisual in obseleteBpVisuals)
                {
                    BasePairVisualsByFivePrimeIndex.Remove(bpVisual.Data.FivePrimeIndex);
                    BasePairVisualsByThreePrimeIndex.Remove(bpVisual.Data.ThreePrimeIndex);
                }

                EditStack.Push(new StructureModelEdit() { EditCategory = StructureModelEdit.EditType.Model });

                //Recreate the relevant visuals.
                CreateBasePairVisuals(Enumerable.Empty<IBasePairBioEntity>()); //This is really to trigger updates to the selection groups.
                CreateHelixVisuals();
                CreateLoopVisuals();
                RenderOnStructureModelChange();
                FireDrawingDirtyStateChanged();
                return true;
            }
            else { return false; }
        }

        public bool CreateHelix(int fivePrimeStart, int threePrimeEnd, int length)
        {
            if(fivePrimeStart < 0 ||
                threePrimeEnd < 0 ||
                length < 0 ||
                (threePrimeEnd - (length - 1)) < (fivePrimeStart + (length - 1)))
                return false;

            List<KeyValuePair<int, int>> newPairings = new List<KeyValuePair<int, int>>();
            newPairings.Add(new KeyValuePair<int, int>(fivePrimeStart, threePrimeEnd));
            for (int i = 1; i < length; i++)
                newPairings.Add(new KeyValuePair<int, int>(fivePrimeStart + i, threePrimeEnd - i));
            return CreateBasePairs(newPairings);
        }

        public bool CreateBasePairs(List<KeyValuePair<int, int>> newPairings)
        {
            if (StructureDrawingViewModel.CurrentStructure.Entity.AddBasePairs(newPairings, true))
            {
                //Now, we may have to remove some existing base pairs as this method uses the force option
                DisconnectExtentsForModelEdits();

                //Were going to try to identify any existing visuals that correspond to currently valid base pairs
                var unmodifiedBasePairVisuals = from bp in StructureDrawingViewModel.CurrentStructure.Entity.BasePairs
                                                from bpVisual in BasePairVisualsByFivePrimeIndex.Values
                                                where bp.FivePrimeIndex == bpVisual.Data.FivePrimeIndex &&
                                                bp.ThreePrimeIndex == bpVisual.Data.ThreePrimeIndex
                                                select bpVisual;
                Dictionary<int, BasePairStructureElementVisualComponent> localunmodifiedBasePairsByFivePrimeIndex = new Dictionary<int, BasePairStructureElementVisualComponent>();
                foreach (var unmodifiedBasePairVisual in unmodifiedBasePairVisuals)
                    localunmodifiedBasePairsByFivePrimeIndex.Add(unmodifiedBasePairVisual.Data.FivePrimeIndex, unmodifiedBasePairVisual);

                //We need to see if we removed any base pairs (i.e., do we have obsolete bp visuals)
                List<BasePairStructureElementVisualComponent> obseleteBpVisuals = BasePairVisualsByFivePrimeIndex.Values.Except(unmodifiedBasePairVisuals).ToList<BasePairStructureElementVisualComponent>();
                List<IStructureElementSelectionGroup> bpSelectionGroups = ElementSelectionGroups[StructureElementSelectionMode.BasePair];
                var obseleteBpSelGroups = (from bpVisual in obseleteBpVisuals
                                           from selgroup in bpSelectionGroups
                                           where bpVisual == selgroup.Visual
                                           select selgroup).ToList();
                foreach (var obseleteBpSelGroup in obseleteBpSelGroups)
                    bpSelectionGroups.Remove(obseleteBpSelGroup);

                if (EditStack.Count > 0)
                {
                    List<StructureModelEdit> editStackCopy = new List<StructureModelEdit>();
                    while (EditStack.Count > 0)
                        editStackCopy.Insert(0, EditStack.Pop());

                    foreach (var obseleteBpVisual in obseleteBpVisuals)
                    {
                        foreach (var edit in editStackCopy)
                        {
                            if (edit.EditCategory == StructureModelEdit.EditType.Properties &&
                                edit.VisualsWithPropertyEdits != null &&
                                edit.VisualsWithPropertyEdits.Contains(obseleteBpVisual))
                                edit.VisualsWithPropertyEdits.Remove(obseleteBpVisual);
                        }
                    }

                    //Put back any edits that still have state.
                    foreach (var edit in editStackCopy)
                    {
                        if (edit.EditCategory == StructureModelEdit.EditType.Properties)
                        {
                            if (edit.VisualsWithPropertyEdits.Count > 0)
                                EditStack.Push(edit);
                        }
                        else
                            EditStack.Push(edit);
                    }
                }

                foreach (var bpVisual in obseleteBpVisuals)
                {
                    BasePairVisualsByFivePrimeIndex.Remove(bpVisual.Data.FivePrimeIndex);
                    BasePairVisualsByThreePrimeIndex.Remove(bpVisual.Data.ThreePrimeIndex);
                }

                EditStack.Push(new StructureModelEdit() { EditCategory = StructureModelEdit.EditType.Model });

                //Identify the new pairings that don't currently have any visual representation
                var pairingsWithoutVisuals = StructureDrawingViewModel.CurrentStructure.Entity.BasePairs.Where(bp => !BasePairVisualsByFivePrimeIndex.ContainsKey(bp.FivePrimeIndex));
                CreateBasePairVisuals(pairingsWithoutVisuals);
                CreateHelixVisuals();
                CreateLoopVisuals();
                RenderOnStructureModelChange();
                FireDrawingDirtyStateChanged();
                return true;
            }
            return false;
        }

        //Updates the StructureModel data to reflect the latest state in the Editor without Marking that "clean"
        public void Synchronize()
        {
            //1. Any nucleotide properties changed?
            StructureDrawingViewModel.CurrentStructure.Entity.NucleotidePropertyEdits.Clear();
            var dirtyNucleotides = NucleotideVisualsBySequenceIndex.Where(ntVisual => ntVisual.Value.IsDirty());
            foreach (var dirtyNucleotide in dirtyNucleotides)
            {
                var newmetadata = dirtyNucleotide.Value.SynchronizeProperties() as NucleotideMetadata;
                if (newmetadata != null)
                {
                    StructureDrawingViewModel.CurrentStructure.Entity.NucleotidePropertyEdits.Add(dirtyNucleotide.Value.NucleotideIndex, newmetadata);
                }
            }

            //2. Any base pair properties changed?
            StructureDrawingViewModel.CurrentStructure.Entity.BasePairPropertyEdits.Clear();
            var dirtyBasePairs = BasePairVisualsByFivePrimeIndex.Where(bpVisual => bpVisual.Value.IsDirty());
            foreach (var dirtyBasePair in dirtyBasePairs)
            {
                var newmetadata = dirtyBasePair.Value.SynchronizeProperties() as BasePairMetadata;
                if (newmetadata != null)
                {
                    StructureDrawingViewModel.CurrentStructure.Entity.BasePairPropertyEdits.Add(dirtyBasePair.Value.Data.FivePrimeIndex, newmetadata);
                }
            }

            //3. Any structure model properties changed?
            StructureDrawingViewModel.CurrentStructure.Entity.StructureModelPropertyEdits = null;
            if (StructureDrawingViewModel.IsDirty())
            {
                var newmodelmetadata = StructureDrawingViewModel.SynchronizeProperties() as StructureModelMetadata;
                if (newmodelmetadata != null)
                    StructureDrawingViewModel.CurrentStructure.Entity.StructureModelPropertyEdits = newmodelmetadata;
            }

            //4. Any nucleotide visuals changed positions?
            var drawingOperations = EditStack.Where(edit => edit.EditCategory == StructureModelEdit.EditType.Drawing);
            Dictionary<int, NucleotideStructureElementVisualComponent> editedNucleotides = new Dictionary<int, NucleotideStructureElementVisualComponent>();
            foreach (var edit in drawingOperations)
            {
                if (edit.VisualDrawingEdits != null)
                {
                    foreach (var drawingEdit in edit.VisualDrawingEdits)
                    {
                        foreach (var ntVisual in drawingEdit.NucleotideVisualsRepositionedByDelta)
                        {
                            if (!editedNucleotides.ContainsKey(ntVisual.NucleotideIndex))
                                editedNucleotides.Add(ntVisual.NucleotideIndex, ntVisual);
                        }
                    }
                }
            }

            if (editedNucleotides.Count > 0)
            {
                SequenceRendering newRendering = new SequenceRendering();
                foreach (var ntIdx in NucleotideVisualsBySequenceIndex.Keys)
                {
                    LocalNucleotideSequenceVector ntLocation = (!editedNucleotides.ContainsKey(ntIdx)) ? new LocalNucleotideSequenceVector(NucleotideVisualsBySequenceIndex[ntIdx].Location) :
                        new LocalNucleotideSequenceVector(editedNucleotides[ntIdx].Location);
                    newRendering.RenderingMap.Add(ntIdx, ntLocation);
                }
                StructureDrawingViewModel.CurrentStructure.Entity.SequenceRenderingEdits = newRendering;
            }
        }

        //Updates the StructureModel data to reflect the latest state in the Editor without Marking it "clean"
        public void Save()
        {
            //1. Any nucleotide properties changed?
            StructureDrawingViewModel.CurrentStructure.Entity.NucleotidePropertyEdits.Clear();
            var dirtyNucleotides = NucleotideVisualsBySequenceIndex.Where(ntVisual => ntVisual.Value.IsDirty());
            foreach (var dirtyNucleotide in dirtyNucleotides)
            {
                var newmetadata = dirtyNucleotide.Value.SaveProperties() as NucleotideMetadata;
                if (newmetadata != null)
                {
                    StructureDrawingViewModel.CurrentStructure.Entity.NucleotidePropertyEdits.Add(dirtyNucleotide.Value.NucleotideIndex, newmetadata);
                }
            }

            //2. Any base pair properties changed?
            StructureDrawingViewModel.CurrentStructure.Entity.BasePairPropertyEdits.Clear();
            var dirtyBasePairs = BasePairVisualsByFivePrimeIndex.Where(bpVisual => bpVisual.Value.IsDirty());
            foreach (var dirtyBasePair in dirtyBasePairs)
            {
                var newmetadata = dirtyBasePair.Value.SaveProperties() as BasePairMetadata;
                if (newmetadata != null)
                {
                    StructureDrawingViewModel.CurrentStructure.Entity.BasePairPropertyEdits.Add(dirtyBasePair.Value.Data.FivePrimeIndex, newmetadata);
                }
            }

            //3. Any structure model properties changed?
            StructureDrawingViewModel.CurrentStructure.Entity.StructureModelPropertyEdits = null;
            if (StructureDrawingViewModel.IsDirty())
            {
                var newmodelmetadata = StructureDrawingViewModel.SaveProperties() as StructureModelMetadata;
                if (newmodelmetadata != null)
                    StructureDrawingViewModel.CurrentStructure.Entity.StructureModelPropertyEdits = newmodelmetadata;
            }

            //4. Any nucleotide visuals changed positions?
            var drawingOperations = EditStack.Where(edit => edit.EditCategory == StructureModelEdit.EditType.Drawing);
            Dictionary<int, NucleotideStructureElementVisualComponent> editedNucleotides = new Dictionary<int, NucleotideStructureElementVisualComponent>();
            foreach (var edit in drawingOperations)
            {
                if (edit.VisualDrawingEdits != null)
                {
                    foreach (var drawingEdit in edit.VisualDrawingEdits)
                    {
                        foreach (var ntVisual in drawingEdit.NucleotideVisualsRepositionedByDelta)
                        {
                            if (!editedNucleotides.ContainsKey(ntVisual.NucleotideIndex))
                                editedNucleotides.Add(ntVisual.NucleotideIndex, ntVisual);
                        }
                    }
                }
            }

            if (editedNucleotides.Count > 0)
            {
                SequenceRendering newRendering = new SequenceRendering();
                foreach (var ntIdx in NucleotideVisualsBySequenceIndex.Keys)
                {
                    LocalNucleotideSequenceVector ntLocation = (!editedNucleotides.ContainsKey(ntIdx)) ? new LocalNucleotideSequenceVector(NucleotideVisualsBySequenceIndex[ntIdx].Location) :
                        new LocalNucleotideSequenceVector(editedNucleotides[ntIdx].Location);
                    newRendering.RenderingMap.Add(ntIdx, ntLocation);
                }
                StructureDrawingViewModel.CurrentStructure.Entity.SequenceRenderingEdits = newRendering;
            }

            EditStack.Clear();
            FireDrawingDirtyStateChanged();
        }

        public void UndoLast()
        {
            if (EditStack.Count() > 0)
            {
                var lastEditSet = EditStack.Pop();
                if (lastEditSet != null)
                {
                    if (lastEditSet.EditCategory == StructureModelEdit.EditType.Properties)
                    {
                        foreach (var visualPropertyEdit in lastEditSet.VisualsWithPropertyEdits)
                            visualPropertyEdit.UndoLastPropertyChange();
                    }
                    else if (lastEditSet.EditCategory == StructureModelEdit.EditType.Model)
                    {
                        if (StructureDrawingViewModel.CurrentStructure.Entity.ModelIsDirty)
                        {
                            StructureDrawingViewModel.CurrentStructure.Entity.UndoLastModelChange();

                            //The best approach is to 1. Identify the visuals the remain unchanged, then identify the visuals the are deleted
                            //and finally identify the new visuals.
                            //Now, we may have to remove some existing base pairs as this method uses the force option
                            DisconnectExtentsForModelEdits();

                            //Were going to try to identify any existing visuals that correspond to currently valid base pairs
                            var unmodifiedBasePairVisuals = from bp in StructureDrawingViewModel.CurrentStructure.Entity.BasePairs
                                                            from bpVisual in BasePairVisualsByFivePrimeIndex.Values
                                                            where bp.FivePrimeIndex == bpVisual.Data.FivePrimeIndex &&
                                                            bp.ThreePrimeIndex == bpVisual.Data.ThreePrimeIndex
                                                            select bpVisual;
                            Dictionary<int, BasePairStructureElementVisualComponent> localunmodifiedBasePairsByFivePrimeIndex = new Dictionary<int, BasePairStructureElementVisualComponent>();
                            foreach (var unmodifiedBasePairVisual in unmodifiedBasePairVisuals)
                                localunmodifiedBasePairsByFivePrimeIndex.Add(unmodifiedBasePairVisual.Data.FivePrimeIndex, unmodifiedBasePairVisual);

                            //Here, we try to identify any base pair visuals that might have been added in this model edit and remove them.
                            List<BasePairStructureElementVisualComponent> obseleteBpVisuals = BasePairVisualsByFivePrimeIndex.Values.Except(unmodifiedBasePairVisuals).ToList<BasePairStructureElementVisualComponent>();
                            List<IStructureElementSelectionGroup> bpSelectionGroups = ElementSelectionGroups[StructureElementSelectionMode.BasePair];
                            var obseleteBpSelGroups = (from bpVisual in obseleteBpVisuals
                                                       from selgroup in bpSelectionGroups
                                                       where bpVisual == selgroup.Visual
                                                       select selgroup).ToList();
                            foreach (var obseleteBpSelGroup in obseleteBpSelGroups)
                                bpSelectionGroups.Remove(obseleteBpSelGroup);

                            foreach (var bpVisual in obseleteBpVisuals)
                            {
                                BasePairVisualsByFivePrimeIndex.Remove(bpVisual.Data.FivePrimeIndex);
                                BasePairVisualsByThreePrimeIndex.Remove(bpVisual.Data.ThreePrimeIndex);
                            }

                            //Here, we try to identify any base pair visuals that might have been deleted in this model edit.
                            var pairingsWithoutVisuals = StructureDrawingViewModel.CurrentStructure.Entity.BasePairs.Where(bp => !BasePairVisualsByFivePrimeIndex.ContainsKey(bp.FivePrimeIndex));
                            CreateBasePairVisuals(pairingsWithoutVisuals);
                            CreateHelixVisuals();
                            CreateLoopVisuals();
                            RenderOnStructureModelChange();
                        }
                    }
                    else if (lastEditSet.EditCategory == StructureModelEdit.EditType.Drawing)
                    {
                        if (lastEditSet.VisualDrawingEdits != null)
                        {
                            foreach (var drawingEdit in lastEditSet.VisualDrawingEdits)
                            {
                                foreach (var editedNt in drawingEdit.NucleotideVisualsRepositionedByDelta)
                                {
                                    Point undoDelta = new Point(drawingEdit.MoveDelta.X * -1, drawingEdit.MoveDelta.Y * -1);
                                    MoveNucleotideBy(editedNt.NucleotideIndex, undoDelta, true, true, true);
                                }
                            }
                        }
                        Rect resetDrawingBounds = Rect.Empty;
                        foreach (var ntVisual in NucleotideVisualsBySequenceIndex.Values)
                            resetDrawingBounds.Union(ntVisual.Bounds);
                        if (!resetDrawingBounds.Equals(DrawingBounds))
                        {
                            double shiftX = (CanvasPadding.X > 0.0) ? (CanvasPadding.X - resetDrawingBounds.Left) : (0.0 - resetDrawingBounds.Left);
                            double shiftY = (CanvasPadding.Y > 0.0) ? (CanvasPadding.Y - resetDrawingBounds.Top) : (0.0 - resetDrawingBounds.Top);
                            ShiftRendering(new Point(shiftX, shiftY));
                        }
                        //CheckAndUpdateRenderingTopLeft(false);
                        //FireDrawingBoundsChangedUpdate();
                    }
                }
                FireDrawingDirtyStateChanged();
            }
        }

        public Rect GetLocationForNucleotide(int ntID)
        {
            if (NucleotideVisualsBySequenceIndex.ContainsKey(ntID))
            {
                return NucleotideVisualsBySequenceIndex[ntID].Bounds;
            }
            return Rect.Empty;
        }

        public void ApplyPropertyChangesToAllNucleotides(StructureElementPropertyChangeSet changeSet)
        {
            if (changeSet == null)
                return;

            if (changeSet.ChangesRecorded <= 0)
                return;

            ApplyPropertyChangesToNucleotidesFiltered(changeSet, new List<IBioSymbol>());
        }

        public void ApplyPropertyChangesToNucleotidesFiltered(StructureElementPropertyChangeSet changeSet, List<IBioSymbol> nucleotideTypeFilter)
        {
            if (changeSet == null)
                return;

            if (changeSet.ChangesRecorded <= 0)
                return;

            var filteredNucleotides = (nucleotideTypeFilter != null && nucleotideTypeFilter.Count > 0) ? (from smNt in NucleotideVisualsBySequenceIndex.Values
                                                                                                          from filterNt in nucleotideTypeFilter
                                                                                                          where smNt.Data.Value == filterNt.Value
                                                                                                          select smNt) : NucleotideVisualsBySequenceIndex.Values;
            List<IStructureElementVisualComponent> editedNucleotides = new List<IStructureElementVisualComponent>();
            foreach (var ntVisual in filteredNucleotides)
            {
                //ntVisual.InterruptEditNotification();
                ntVisual.ApplyPropertyEditSet(changeSet, true);
                //ntVisual.UninterruptEditNotification();
                editedNucleotides.Add(ntVisual);
            }

            if (editedNucleotides.Count > 0)
            {
                StructureModelEdit edit = new StructureModelEdit()
                {
                    EditCategory = StructureModelEdit.EditType.Properties,
                    VisualsWithPropertyEdits = editedNucleotides
                };
                EditStack.Push(edit);
                FireDrawingDirtyStateChanged();
            }
        }

        public void ApplyPropertyChangesToNucleotide(int ntID, StructureElementPropertyChangeSet changeSet)
        {
            if (changeSet == null)
                return;

            if (changeSet.ChangesRecorded <= 0)
                return;

            Debug.Assert(NucleotideVisualsBySequenceIndex != null);
            if (!NucleotideVisualsBySequenceIndex.ContainsKey(ntID))
                throw new ArgumentException(string.Format("Error: Invalid Nucleotide ID of {0}", ntID));

            NucleotideStructureElementVisualComponent ntVisual = NucleotideVisualsBySequenceIndex[ntID];
            ntVisual.ApplyPropertyEditSet(changeSet, false);
        }

        public Point MoveNucleotideTo(int ntID, Point location, bool updateBasePairs, bool updateHelices, bool updateLoops)
        {
            Debug.Assert(NucleotideVisualsBySequenceIndex != null);
            if (!NucleotideVisualsBySequenceIndex.ContainsKey(ntID))
                throw new ArgumentException(string.Format("Error: Invalid Nucleotide ID of {0}", ntID));

            NucleotideStructureElementVisualComponent ntVisual = NucleotideVisualsBySequenceIndex[ntID];
            Point locationAsDelta = new Point(location.X - ntVisual.Location.X, location.Y - ntVisual.Location.Y);
            MoveNucleotideBy(ntID, locationAsDelta, updateBasePairs, updateHelices, updateLoops);
            return locationAsDelta;
        }

        public bool MoveNucleotideBy(int ntID, Point delta, bool updateBasePairs, bool updateHelices, bool updateLoops)
        {
            Debug.Assert(NucleotideVisualsBySequenceIndex != null);
            if (!NucleotideVisualsBySequenceIndex.ContainsKey(ntID))
                throw new ArgumentException(string.Format("Error: Invalid Nucleotide ID of {0}", ntID));

            NucleotideStructureElementVisualComponent ntVisual = NucleotideVisualsBySequenceIndex[ntID];
            //Rect movePreview = ntVisual.PreviewMoveBy(delta);
            /*if (EnforceBounds && !CurrentBounds.Contains(movePreview))
                return false;*/

            //Move the nucleotide
            ntVisual.MoveBy(delta);

            //Increase the drawing bounds if necessary.
            CheckAndUpdateBounds(ntVisual.Bounds);

            //Update the local rendering map
            IVector ntVector = CurrentRenderingState.RenderingMap[ntVisual.NucleotideIndex];
            ntVector.StartX = ntVisual.Location.X;
            ntVector.StartY = ntVisual.Location.Y;
            ntVector.RayAngle = ntVisual.NucleotideConnector.RayAngle;
            ntVector.Magnitude = ntVisual.NucleotideConnector.Distance;

            //Update the connected nucleotides
            NucleotideStructureElementVisualComponent prevNtVisual = ((ntVisual.NucleotideIndex - 1) >= 0) ? NucleotideVisualsBySequenceIndex[ntVisual.NucleotideIndex - 1] :
                NucleotideVisualsBySequenceIndex[StructureDrawingViewModel.CurrentStructure.Entity.Sequence.SequenceData.Count - 1];
            prevNtVisual.UpdateConnectedNucleoidePositionMoveBy(delta);
            IVector prevNtVector = CurrentRenderingState.RenderingMap[ntVisual.NucleotideIndex];
            prevNtVector.RayAngle = prevNtVisual.NucleotideConnector.RayAngle;
            prevNtVector.Magnitude = prevNtVisual.NucleotideConnector.Distance;

            NucleotideStructureElementVisualComponent nextNtVisual = ((ntVisual.NucleotideIndex + 1) < StructureDrawingViewModel.CurrentStructure.Entity.Sequence.SequenceData.Count) ? NucleotideVisualsBySequenceIndex[ntVisual.NucleotideIndex + 1] :
                NucleotideVisualsBySequenceIndex[0];
            nextNtVisual.UpdatePreviousNucleotidePositionMoveBy(delta);

            //Update the parent pairing if paired and requested to
            if (updateBasePairs && BasePairVisualsByFivePrimeIndex.ContainsKey(ntVisual.NucleotideIndex))
            {
                BasePairStructureElementVisualComponent bpVisual = BasePairVisualsByFivePrimeIndex[ntVisual.NucleotideIndex];
                bpVisual.FivePrimeNucleotideMovedBy(delta);
            }

            if (updateBasePairs && BasePairVisualsByThreePrimeIndex.ContainsKey(ntVisual.NucleotideIndex))
            {
                BasePairStructureElementVisualComponent bpVisual = BasePairVisualsByThreePrimeIndex[ntVisual.NucleotideIndex];
                bpVisual.ThreePrimeNucleotideMovedBy(delta);
            }

            //Update the helix the parent pairing is part of
            if (updateHelices && HelixVisualsByContainingNucleotideIndex.ContainsKey(ntVisual.NucleotideIndex))
            {
                HelixStructureElementVisualComponent helixVisual = HelixVisualsByContainingNucleotideIndex[ntVisual.NucleotideIndex];
                helixVisual.HelixNucleotideMovedBy(ntVisual.NucleotideIndex, delta);
            }

            //Update the loop if the nucleotide is part of a loop
            if (updateLoops && LoopVisualsByContainingNucleotideIndex.ContainsKey(ntVisual.NucleotideIndex))
            {
                LoopStructureElementVisualComponent loopVisual = LoopVisualsByContainingNucleotideIndex[ntVisual.NucleotideIndex];
                loopVisual.LoopNucleotideMovedBy(ntVisual.NucleotideIndex, delta);
            }

            return true;
        }

        public void ApplyPropertyChangesToBasePair(int bpID, StructureElementPropertyChangeSet changeSet)
        {
            if (changeSet == null)
                return;

            if (changeSet.ChangesRecorded <= 0)
                return;

            Debug.Assert(BasePairVisualsByFivePrimeIndex != null);
            if (!BasePairVisualsByFivePrimeIndex.ContainsKey(bpID))
                throw new ArgumentException(string.Format("Error: Invalid Base Pair ID of {0}", bpID));

            BasePairStructureElementVisualComponent bpVisual = BasePairVisualsByFivePrimeIndex[bpID];
            bpVisual.ApplyPropertyEditSet(changeSet, false);
        }

        public void ApplyPropertyChangesToAllBasePairs(StructureElementPropertyChangeSet changeSet)
        {
            if (changeSet == null)
                return;

            if (changeSet.ChangesRecorded <= 0)
                return;

            ApplyPropertyChangesToBasePairsFiltered(changeSet, new List<string>());
        }

        public void ApplyPropertyChangesToBasePairsFiltered(StructureElementPropertyChangeSet changeSet, List<string> basePairTypeFilter)
        {
            if (changeSet == null)
                return;

            if (changeSet.ChangesRecorded <= 0)
                return;

            var filteredBasePairs = (basePairTypeFilter != null && basePairTypeFilter.Count > 0) ? (from bpVisual in BasePairVisualsByFivePrimeIndex.Values
                                                                                                    from bpText in basePairTypeFilter
                                                                                                    where bpVisual.Data.Text == bpText
                                                                                                    select bpVisual) : BasePairVisualsByFivePrimeIndex.Values;
            List<IStructureElementVisualComponent> editedBasePairs = new List<IStructureElementVisualComponent>();
            foreach (var bpVisual in filteredBasePairs)
            {
                //ntVisual.InterruptEditNotification();
                bpVisual.ApplyPropertyEditSet(changeSet, true);
                //ntVisual.UninterruptEditNotification();
                editedBasePairs.Add(bpVisual);
            }

            if (editedBasePairs.Count > 0)
            {
                StructureModelEdit edit = new StructureModelEdit()
                {
                    EditCategory = StructureModelEdit.EditType.Properties,
                    VisualsWithPropertyEdits = editedBasePairs
                };
                EditStack.Push(edit);
                FireDrawingDirtyStateChanged();
            }
        }

        public Dictionary<int, Point> ChangeBasePairsWidth(double desiredWidth, Func<IBasePairBioEntity, bool> basePairTypeFilter)
        {
            if (desiredWidth <= 0.0)
                throw new ArgumentException(string.Format("Error: Width must be postive and greater than zero for ChangeBasePairsWidth, width specified: {0}", desiredWidth));

            Dictionary<int, Point> allMods = new Dictionary<int, Point>();
            var filteredBasePairs = BasePairVisualsByFivePrimeIndex.Values.Where(bpVisual => basePairTypeFilter(bpVisual.Data));
            foreach (var bpVisual in filteredBasePairs)
            {
                Point threePrimeDelta = ChangeBasePairWidth(bpVisual.Data.FivePrimeIndex, desiredWidth);
                allMods.Add(bpVisual.Data.ThreePrimeIndex, threePrimeDelta);
            }

            return allMods;
        }

        public Dictionary<int, Point> ChangeAllBasePairsWidth(double desiredWidth)
        {
            if (desiredWidth <= 0.0)
                throw new ArgumentException(string.Format("Error: Width must be postive and greater than zero for ChangeAllBasePairsWidth, width specified: {0}", desiredWidth));

            Dictionary<int, Point> allMods = new Dictionary<int, Point>();
            foreach (var bpId in BasePairVisualsByFivePrimeIndex.Keys)
            {
                Point threePrimeDelta = ChangeBasePairWidth(bpId, desiredWidth);
                allMods.Add(BasePairVisualsByFivePrimeIndex[bpId].Data.ThreePrimeIndex, threePrimeDelta);
            }

            return allMods;
        }

        public Point ChangeBasePairWidth(int bpID, double desiredWidth)
        {
            Debug.Assert(BasePairVisualsByFivePrimeIndex != null);
            if (!BasePairVisualsByFivePrimeIndex.ContainsKey(bpID))
                throw new ArgumentException(string.Format("Error: Invalid Base Pair ID of {0}", bpID));

            BasePairStructureElementVisualComponent bpVisual = BasePairVisualsByFivePrimeIndex[bpID];
            Point threePrimeDelta = bpVisual.ChangeBasePairWidth(desiredWidth);
            MoveNucleotideBy(bpVisual.Data.ThreePrimeIndex, threePrimeDelta, false, true, false);

            CheckAndUpdateBounds(bpVisual.Bounds);
            return threePrimeDelta;
        }

        /// <summary>
        /// Supported angles between 0 and 360.0 inclusive.
        /// </summary>
        /// <param name="bpID"></param>
        /// <param name="angleInDegrees"></param>
        /// <returns></returns>
        public Point RotateBasePairTo(int bpID, double angleInDegrees)
        {
            if (!BasePairVisualsByFivePrimeIndex.ContainsKey(bpID))
                throw new ArgumentException(string.Format("Error: Invalid Base Pair ID of {0}", bpID));

            BasePairStructureElementVisualComponent bpVisual = BasePairVisualsByFivePrimeIndex[bpID];
            Point threePrimeDelta = bpVisual.RotateBasePairTo(angleInDegrees);
            MoveNucleotideBy(bpVisual.Data.ThreePrimeIndex, threePrimeDelta, false, true, true);

            CheckAndUpdateBounds(bpVisual.Bounds);
            return threePrimeDelta;
        }

        /// <summary>
        /// Supported rotations between -360.0 and +360.0
        /// </summary>
        /// <param name="bpID"></param>
        /// <param name="rotationInDegrees"></param>
        /// <returns></returns>
        public Point RotateBasePairBy(int bpID, double rotationInDegrees)
        {
            if(!BasePairVisualsByFivePrimeIndex.ContainsKey(bpID))
                throw new ArgumentException(string.Format("Error: Invalid Base Pair ID of {0}", bpID));

            BasePairStructureElementVisualComponent bpVisual = BasePairVisualsByFivePrimeIndex[bpID];
            Point threePrimeDelta = bpVisual.RotateBasePairBy(rotationInDegrees);
            MoveNucleotideBy(bpVisual.Data.ThreePrimeIndex, threePrimeDelta, false, true, false);

            CheckAndUpdateBounds(bpVisual.Bounds);
            return threePrimeDelta;
        }

        public Point MoveBasePairTo(int bpID, Point location, bool updateParents)
        {
            Debug.Assert(BasePairVisualsByFivePrimeIndex != null);
            if (!BasePairVisualsByFivePrimeIndex.ContainsKey(bpID))
                throw new ArgumentException(string.Format("Error: Invalid Base Pair ID of {0}", bpID));

            BasePairStructureElementVisualComponent bpVisual = BasePairVisualsByFivePrimeIndex[bpID];
            NucleotideStructureElementVisualComponent fivePrimeNtVisual = NucleotideVisualsBySequenceIndex[bpVisual.Data.FivePrimeIndex];
            Point locationAsDelta = new Point(location.X - fivePrimeNtVisual.Location.X, location.Y - fivePrimeNtVisual.Location.Y);
            MoveBasePairBy(bpID, locationAsDelta, updateParents);
            return locationAsDelta;
        }

        public bool MoveBasePairBy(int bpID, Point delta, bool updateParents)
        {
            Debug.Assert(BasePairVisualsByFivePrimeIndex != null);
            if (!BasePairVisualsByFivePrimeIndex.ContainsKey(bpID))
                throw new ArgumentException(string.Format("Error: Invalid Base Pair ID of {0}", bpID));

            BasePairStructureElementVisualComponent bpVisual = BasePairVisualsByFivePrimeIndex[bpID];
            //Rect movePreview = bpVisual.PreviewMoveBy(delta);
            /*if (EnforceBounds && !CurrentBounds.Contains(movePreview))
                return false;*/

            if (!MoveNucleotideBy(bpVisual.Data.FivePrimeIndex, delta, false, false, false))
                return false;
            if (!MoveNucleotideBy(bpVisual.Data.ThreePrimeIndex, delta, false, false, false))
                return false;

            bpVisual.MoveBy(delta);

            //Update helix visual if necessary?
            if (updateParents && (HelixVisualsByContainingNucleotideIndex.ContainsKey(bpVisual.Data.FivePrimeIndex) &&
                HelixVisualsByContainingNucleotideIndex.ContainsKey(bpVisual.Data.ThreePrimeIndex)) &&
                HelixVisualsByContainingNucleotideIndex[bpVisual.Data.FivePrimeIndex].FivePrimeStartNucleotideIndex == HelixVisualsByContainingNucleotideIndex[bpVisual.Data.ThreePrimeIndex].FivePrimeStartNucleotideIndex)
            {
                HelixVisualsByContainingNucleotideIndex[bpVisual.Data.FivePrimeIndex].HelixBasePairMovedBy(bpVisual.Data.FivePrimeIndex, bpVisual.Data.ThreePrimeIndex, delta);
            }

            //Increment the drawing bounds if necessary
            CheckAndUpdateBounds(bpVisual.Bounds);

            return true;
        }

        public bool MoveHelixBy(int helixID, Point delta)
        {
            Debug.Assert(HelixVisualsByFivePrimeStartIndex != null);
            if (!HelixVisualsByFivePrimeStartIndex.ContainsKey(helixID))
                throw new ArgumentException(string.Format("Error: Invalid Helix ID of {0} to MoveHelixBy", helixID));

            HelixStructureElementVisualComponent helixVisual = HelixVisualsByFivePrimeStartIndex[helixID];

            foreach (var basePair in helixVisual.BasePairs)
            {
                if (!MoveBasePairBy(basePair.FivePrimeIndex, delta, false))
                    return false;
            }

            helixVisual.MoveBy(delta); //Were assuming that if all the underlying bp can make the move, then the helix that comprises them can make the move.

            return true;
        }

        public Point MoveHelixTo(int helixID, Point location)
        {
            Debug.Assert(HelixVisualsByFivePrimeStartIndex != null);
            if (!HelixVisualsByFivePrimeStartIndex.ContainsKey(helixID))
                throw new ArgumentException(string.Format("Error: Invalid Helix ID of {0} to MoveHelixBy", helixID));

            HelixStructureElementVisualComponent helixVisual = HelixVisualsByFivePrimeStartIndex[helixID];
            NucleotideStructureElementVisualComponent fivePrimeStartNtVisual = NucleotideVisualsBySequenceIndex[helixID];
            Point locationAsDelta = new Point(location.X - fivePrimeStartNtVisual.Location.X, location.Y - fivePrimeStartNtVisual.Location.Y);
            MoveHelixBy(helixID, locationAsDelta);
            return locationAsDelta;
        }

        public Dictionary<int, Point> ChangeAllHelicesPadding(double pad)
        {
            if (pad < 0.0)
                throw new ArgumentException(string.Format("Error: Padding must be greater than zero for PadAllHelices, pad specified: {0}", pad));

            Dictionary<int, Point> allPadMods = new Dictionary<int, Point>();
            foreach (var helixID in HelixVisualsByFivePrimeStartIndex.Keys)
            {
                Dictionary<int, Point> padModsForHelix = PadHelix(helixID, pad);
                foreach (var ntID in padModsForHelix.Keys)
                {
                    if (!allPadMods.ContainsKey(ntID))
                        allPadMods.Add(ntID, padModsForHelix[ntID]);
                }
            }
            return allPadMods;
        }

        public Dictionary<int, Point> PadHelix(int helixID, double pad)
        {
            Debug.Assert(HelixVisualsByFivePrimeStartIndex != null);
            if (!HelixVisualsByFivePrimeStartIndex.ContainsKey(helixID))
                throw new ArgumentException(string.Format("Error: Invalid Helix ID of {0} to PadHelix", helixID));

            if (pad <= 0.0)
                throw new ArgumentException(string.Format("Error: Padding must be postive and greater than zero for PadHelix, pad specified: {0}", pad));

            HelixStructureElementVisualComponent helixVisual = HelixVisualsByFivePrimeStartIndex[helixID];
            Dictionary<int, Point> padMods = helixVisual.ChangeHelixPadding(pad);

            foreach (var ntID in padMods.Keys)
            {
                MoveNucleotideBy(ntID, padMods[ntID], true, false, false);
            }

            return padMods;
        }

        public Dictionary<int, Point> SnapHelix(int helixID, bool toOpeningBasePair)
        {
            Debug.Assert(HelixVisualsByFivePrimeStartIndex != null);
            if (!HelixVisualsByFivePrimeStartIndex.ContainsKey(helixID))
                throw new ArgumentException(string.Format("Error: Invalid Helix ID of {0} to SnapHelix", helixID));

            HelixStructureElementVisualComponent helixVisual = HelixVisualsByFivePrimeStartIndex[helixID];
            Dictionary<int, Point> snapMods = (toOpeningBasePair) ? helixVisual.SnapToOpeningBasePair() : helixVisual.SnapToClosingBasePair();

            foreach (var ntID in snapMods.Keys)
            {
                MoveNucleotideBy(ntID, snapMods[ntID], true, false, false);
            }

            return snapMods;
        }

        public Dictionary<int, Point> RotateHelixBy(int helixID, double rotationInDegrees)
        {
            Debug.Assert(HelixVisualsByFivePrimeStartIndex != null);
            if (!HelixVisualsByFivePrimeStartIndex.ContainsKey(helixID))
                throw new ArgumentException(string.Format("Error: Invalid Helix ID of {0} to RotateHelixBy", helixID));

            //Rotate the 5' base pair in the helix and then snap the rest of the helix to the rotated 5' base pair.
            Point bpThreePrimeMod = RotateBasePairBy(helixID, rotationInDegrees);
            Dictionary<int, Point> rotationMods = SnapHelix(helixID, true);
            rotationMods.Add(HelixVisualsByFivePrimeStartIndex[helixID].ThreePrimeEndNucleotideIndex, bpThreePrimeMod);

            return rotationMods;
        }

        public Dictionary<int, Point> RotateHelixTo(int helixID, double angleInDegrees)
        {
            Debug.Assert(HelixVisualsByFivePrimeStartIndex != null);
            if (!HelixVisualsByFivePrimeStartIndex.ContainsKey(helixID))
                throw new ArgumentException(string.Format("Error: Invalid Helix ID of {0} to RotateHelixTo", helixID));

            //Rotate the 5' base pair in the helix and then snap the rest of the helix to the rotated 5' base pair.
            double fivePrimeBPAngle = (angleInDegrees >= 0 && angleInDegrees <= 270) ? angleInDegrees + 90.0 : angleInDegrees - 270.0; //If we want the helix at 45 degrees, then the 5' base pair is at 135 degrees.
            Point bpThreePrimeMod = RotateBasePairTo(helixID, fivePrimeBPAngle);
            Dictionary<int, Point> rotationMods = SnapHelix(helixID, true);
            rotationMods.Add(HelixVisualsByFivePrimeStartIndex[helixID].ThreePrimeEndNucleotideIndex, bpThreePrimeMod);

            return rotationMods;
        }

        public Dictionary<int, Point> ChangeAllHelicesWidth(double desiredWidth)
        {
            if (desiredWidth <= 0.0)
                throw new ArgumentException(string.Format("Error: Width must be postive and greater than zero for ChangeAllHelicesWidth, width specified: {0}", desiredWidth));

            Dictionary<int, Point> allWidthMods = new Dictionary<int, Point>();
            foreach (var helixID in HelixVisualsByFivePrimeStartIndex.Keys)
            {
                Dictionary<int, Point> widthModsForHelix = ChangeHelixWidth(helixID, desiredWidth);
                foreach (var ntID in widthModsForHelix.Keys)
                {
                    if (!allWidthMods.ContainsKey(ntID))
                        allWidthMods.Add(ntID, widthModsForHelix[ntID]);
                }
            }
            return allWidthMods;

        }

        public Dictionary<int, Point> ChangeHelixWidth(int helixID, double desiredWidth)
        {
            Debug.Assert(HelixVisualsByFivePrimeStartIndex != null);
            if (!HelixVisualsByFivePrimeStartIndex.ContainsKey(helixID))
                throw new ArgumentException(string.Format("Error: Invalid Helix ID of {0} to ChangeHelixWidth", helixID));

            if (desiredWidth <= 0.0)
                throw new ArgumentException(string.Format("Error: Width must be postive and greater than zero for ChangeHelixWidth, width specified: {0}", desiredWidth));

            //Similar to the helix rotations, we will just change the width of the 5' base pair in the helix and then snap the rest of the helix to the adjusted base pair.
            Point bpThreePrimeMod = ChangeBasePairWidth(helixID, desiredWidth);
            Dictionary<int, Point> widthMods = SnapHelix(helixID, true);
            widthMods.Add(HelixVisualsByFivePrimeStartIndex[helixID].ThreePrimeEndNucleotideIndex, bpThreePrimeMod);

            return widthMods;
        }

        public Point MoveLoopTo(int loopID, Point location)
        {
            Debug.Assert(LoopVisualsByStartIndex != null);
            if (!LoopVisualsByStartIndex.ContainsKey(loopID))
                throw new ArgumentException(string.Format("Error: Invalid Loop ID of {0} to MoveLoopBy", loopID));

            LoopStructureElementVisualComponent loopVisual = LoopVisualsByStartIndex[loopID];
            Point firstNtLocation = NucleotideVisualsBySequenceIndex[loopID].Location;
            Point locationAsDelta = new Point(location.X - firstNtLocation.X, location.Y - firstNtLocation.Y);

            MoveLoopBy(loopID, locationAsDelta);
            
            return locationAsDelta;
        }

        public bool MoveLoopBy(int loopID, Point delta)
        {
            Debug.Assert(LoopVisualsByStartIndex != null);
            if (!LoopVisualsByStartIndex.ContainsKey(loopID))
                throw new ArgumentException(string.Format("Error: Invalid Loop ID of {0} to MoveLoopBy", loopID));

            LoopStructureElementVisualComponent loopVisual = LoopVisualsByStartIndex[loopID];

            foreach (var nt in loopVisual.NucleotideIndices)
            {
                if (!MoveNucleotideBy(nt, delta, false, false, false))
                    return false;
            }

            loopVisual.MoveBy(delta); //Were assuming that if all the underlying nt can make the move, then the loop that comprises them can make the move.

            return true;
        }

        public Dictionary<int, Point> SnapLoop(int loopID)
        {
            Debug.Assert(LoopVisualsByStartIndex != null);
            if (!LoopVisualsByStartIndex.ContainsKey(loopID))
                throw new ArgumentException(string.Format("Error: Invalid Loop ID of {0} to MoveLoopBy", loopID));

            LoopStructureElementVisualComponent loopVisual = LoopVisualsByStartIndex[loopID];
            Dictionary<int, Point> snapMods = loopVisual.Snap();

            foreach (var ntIdx in snapMods.Keys)
            {
                MoveNucleotideBy(ntIdx, snapMods[ntIdx], false, false, false);
            }

            return snapMods;
        }

        public bool ChangeLoopHeight(int loopID, double desiredHeight)
        {
            Debug.Assert(LoopVisualsByStartIndex != null);
            if (!LoopVisualsByStartIndex.ContainsKey(loopID))
                throw new ArgumentException(string.Format("Error: Invalid Loop ID of {0} to MoveLoopBy", loopID));

            LoopStructureElementVisualComponent loopVisual = LoopVisualsByStartIndex[loopID];
            loopVisual.ChangeLoopHeight(desiredHeight);

            return true;
        }

        public Dictionary<int, Point> ChangeLoopWidth(int loopID, double desiredWidth)
        {
            Debug.Assert(LoopVisualsByStartIndex != null);
            if (!LoopVisualsByStartIndex.ContainsKey(loopID))
                throw new ArgumentException(string.Format("Error: Invalid Loop ID of {0} to MoveLoopBy", loopID));

            LoopStructureElementVisualComponent loopVisual = LoopVisualsByStartIndex[loopID];
            Dictionary<int, Point> widthMods = loopVisual.ChangeLoopWidth(desiredWidth);

            foreach (var ntIdx in widthMods.Keys)
            {
                MoveNucleotideBy(ntIdx, widthMods[ntIdx], false, false, false);
            }

            return widthMods;
        }

        public void RenderOnStructureModelChange()
        {
            //I don't reposition the nucleotides since I want to keep the drawing state.
            Debug.Assert(CurrentRenderingState != null);
            Debug.Assert(CurrentRenderingState.RenderingMap != null);
            Debug.Assert(CurrentRenderingState.RenderingMap.Count == StructureDrawingViewModel.CurrentStructure.Entity.Sequence.SequenceData.Count);

            //Position the base pairs.
            foreach (var basePairVisual in BasePairVisualsByFivePrimeIndex.Values)
            {
                Debug.Assert(basePairVisual.Data != null);
                Debug.Assert(NucleotideVisualsBySequenceIndex.ContainsKey(basePairVisual.Data.FivePrimeIndex) &&
                    NucleotideVisualsBySequenceIndex.ContainsKey(basePairVisual.Data.ThreePrimeIndex));

                NucleotideStructureElementVisualComponent fivePrimeNtVisual = NucleotideVisualsBySequenceIndex[basePairVisual.Data.FivePrimeIndex];
                NucleotideStructureElementVisualComponent threePrimeNtVisual = NucleotideVisualsBySequenceIndex[basePairVisual.Data.ThreePrimeIndex];
                basePairVisual.FivePrimeNucleotideMovedTo(fivePrimeNtVisual.Location);
                basePairVisual.ThreePrimeNucleotideMovedTo(threePrimeNtVisual.Location);
                DrawingBounds = Rect.Union(DrawingBounds, basePairVisual.Bounds);
            }

            //Position the helices.
            foreach (var helixVisual in HelixVisualsByFivePrimeStartIndex.Values)
            {
                Debug.Assert(helixVisual != null);
                Debug.Assert(helixVisual.NucleotideIndices.All(idx => NucleotideVisualsBySequenceIndex.ContainsKey(idx)));

                //For now, we only worry about positioning the edges of the helix shape
                foreach (var ntIdxInHelix in helixVisual.NucleotideIndices)
                {
                    NucleotideStructureElementVisualComponent helixNtVisual = NucleotideVisualsBySequenceIndex[ntIdxInHelix];
                    helixVisual.HelixNucleotideMovedTo(helixNtVisual.NucleotideIndex, helixNtVisual.Location);
                }
            }

            //Position the loops.
            foreach (var loopVisual in LoopVisualsByStartIndex.Values)
            {
                Debug.Assert(loopVisual != null);
                Debug.Assert(loopVisual.NucleotideIndices.All(idx => NucleotideVisualsBySequenceIndex.ContainsKey(idx)));

                foreach (var ntIdxInLoop in loopVisual.NucleotideIndices)
                {
                    NucleotideStructureElementVisualComponent loopNtVisual = NucleotideVisualsBySequenceIndex[ntIdxInLoop];
                    loopVisual.LoopNucleotideMovedTo(loopNtVisual.NucleotideIndex, loopNtVisual.Location);
                }
            }

            FireStructureDrawingVisualPropertiesUpdate();
            CheckAndUpdateRenderingTopLeft(false);
            SetSnapshotBrush();
            SetRangeSelectionGroup();
            SetDomainSelectionGroups();
        }

        public void RenderComplete()
        {
            Debug.Assert(CurrentRenderingState != null);
            Debug.Assert(CurrentRenderingState.RenderingMap != null);
            Debug.Assert(CurrentRenderingState.RenderingMap.Count == StructureDrawingViewModel.CurrentStructure.Entity.Sequence.SequenceData.Count);

            DrawingBounds = Rect.Empty;
            //Position the nucleotides.
            foreach (int ntIndex in CurrentRenderingState.RenderingMap.Keys)
            {
                Debug.Assert(NucleotideVisualsBySequenceIndex.ContainsKey(ntIndex));
                if (NucleotideVisualsBySequenceIndex.ContainsKey(ntIndex))
                {
                    IVector sequenceRenderingInstruction = CurrentRenderingState.RenderingMap[ntIndex];
                    NucleotideStructureElementVisualComponent ntVisual = NucleotideVisualsBySequenceIndex[ntIndex];
                    ntVisual.MoveBy(new Point(sequenceRenderingInstruction.StartX - ntVisual.Location.X, sequenceRenderingInstruction.StartY - ntVisual.Location.Y));
                    DrawingBounds = Rect.Union(DrawingBounds, ntVisual.Bounds);
                }
            }

            //Position the base pairs.
            foreach (var basePairVisual in BasePairVisualsByFivePrimeIndex.Values)
            {
                Debug.Assert(basePairVisual.Data != null);
                Debug.Assert(NucleotideVisualsBySequenceIndex.ContainsKey(basePairVisual.Data.FivePrimeIndex) &&
                    NucleotideVisualsBySequenceIndex.ContainsKey(basePairVisual.Data.ThreePrimeIndex));

                NucleotideStructureElementVisualComponent fivePrimeNtVisual = NucleotideVisualsBySequenceIndex[basePairVisual.Data.FivePrimeIndex];
                NucleotideStructureElementVisualComponent threePrimeNtVisual = NucleotideVisualsBySequenceIndex[basePairVisual.Data.ThreePrimeIndex];
                basePairVisual.FivePrimeNucleotideMovedTo(fivePrimeNtVisual.Location);
                basePairVisual.ThreePrimeNucleotideMovedTo(threePrimeNtVisual.Location);
                DrawingBounds = Rect.Union(DrawingBounds, basePairVisual.Bounds);
            }

            //Position the helices.
            foreach (var helixVisual in HelixVisualsByFivePrimeStartIndex.Values)
            {
                Debug.Assert(helixVisual != null);
                Debug.Assert(helixVisual.NucleotideIndices.All(idx => NucleotideVisualsBySequenceIndex.ContainsKey(idx)));

                //For now, we only worry about positioning the edges of the helix shape
                foreach (var ntIdxInHelix in helixVisual.NucleotideIndices)
                {
                    NucleotideStructureElementVisualComponent helixNtVisual = NucleotideVisualsBySequenceIndex[ntIdxInHelix];
                    helixVisual.HelixNucleotideMovedTo(helixNtVisual.NucleotideIndex, helixNtVisual.Location);
                }
            }

            //Position the loops.
            foreach (var loopVisual in LoopVisualsByStartIndex.Values)
            {
                Debug.Assert(loopVisual != null);
                Debug.Assert(loopVisual.NucleotideIndices.All(idx => NucleotideVisualsBySequenceIndex.ContainsKey(idx)));

                foreach(var ntIdxInLoop in loopVisual.NucleotideIndices)
                {
                    NucleotideStructureElementVisualComponent loopNtVisual = NucleotideVisualsBySequenceIndex[ntIdxInLoop];
                    loopVisual.LoopNucleotideMovedTo(loopNtVisual.NucleotideIndex, loopNtVisual.Location);
                }
            }

            FireStructureDrawingVisualPropertiesUpdate();
            CheckAndUpdateRenderingTopLeft(true);
            SetSnapshotBrush();
            SetRangeSelectionGroup();
            SetDomainSelectionGroups();
            StartEditTracking();
        }

        public StructureDrawingManager(SecondaryStructureDrawing drawingVisual)
        {
            Debug.Assert(drawingVisual != null);
            DrawingBounds = Rect.Empty;
            CanvasBounds = Rect.Empty;
            //EnforceBounds = false;
            UsingDefaultRendering = false;
            StructureDrawingVisual = drawingVisual;
            CanvasPadding = new Point(50, 50);
            
            ElementSelectionGroups = new Dictionary<StructureElementSelectionMode, List<IStructureElementSelectionGroup>>();
            NucleotideVisualsBySequenceIndex = new Dictionary<int, NucleotideStructureElementVisualComponent>();
            NoParentExtentNucleotideVisualsBySequenceIndex = new Dictionary<int, NucleotideStructureElementVisualComponent>();
            BasePairVisualsByFivePrimeIndex = new Dictionary<int, BasePairStructureElementVisualComponent>();
            BasePairVisualsByThreePrimeIndex = new Dictionary<int, BasePairStructureElementVisualComponent>();
            HelixVisualsByFivePrimeStartIndex = new Dictionary<int, HelixStructureElementVisualComponent>();
            HelixVisualsByThreePrimeStartIndex = new Dictionary<int, HelixStructureElementVisualComponent>();
            HelixVisualsByContainingNucleotideIndex = new Dictionary<int, HelixStructureElementVisualComponent>();
            LoopVisualsByStartIndex = new Dictionary<int, LoopStructureElementVisualComponent>();
            LoopVisualsByContainingNucleotideIndex = new Dictionary<int, LoopStructureElementVisualComponent>();
            EditStack = new Stack<StructureModelEdit>();
            DataDrivenDefaultProviders = new List<IDataDrivenDefaultStructureElementPropertyProvider>();
            DataDrivenDefaultProviders.Add(StructureDrawingVisual);
        }

        #region Private Members, Properties and Methods

        private List<IDataDrivenDefaultStructureElementPropertyProvider> DataDrivenDefaultProviders { get; set; }
        private Dictionary<int, NucleotideStructureElementVisualComponent> NucleotideVisualsBySequenceIndex { get; set; }
        private Dictionary<int, NucleotideStructureElementVisualComponent> NoParentExtentNucleotideVisualsBySequenceIndex { get; set; }
        private Dictionary<int, BasePairStructureElementVisualComponent> BasePairVisualsByFivePrimeIndex { get; set; }
        private Dictionary<int, BasePairStructureElementVisualComponent> BasePairVisualsByThreePrimeIndex { get; set; }
        private Dictionary<int, HelixStructureElementVisualComponent> HelixVisualsByFivePrimeStartIndex { get; set; }
        private Dictionary<int, HelixStructureElementVisualComponent> HelixVisualsByThreePrimeStartIndex { get; set; }
        private Dictionary<int, HelixStructureElementVisualComponent> HelixVisualsByContainingNucleotideIndex { get; set; }
        private Dictionary<int, LoopStructureElementVisualComponent> LoopVisualsByStartIndex { get; set; }
        private Dictionary<int, LoopStructureElementVisualComponent> LoopVisualsByContainingNucleotideIndex { get; set; }

        private Stack<StructureModelEdit> EditStack { get; set; }

        private SecondaryStructureDrawing StructureDrawingVisual { get; set; }
        private SecondaryStructureEditorViewModel StructureDrawingViewModel { get; set; }
        //private EditableStructureModel StructureDrawingData { get; set; }
        private SequenceRendering CurrentRenderingState { get; set; }
        private StructureModelMetadata StructureModelProperties { get; set; }
        private bool UsingDefaultRendering { get; set; }
        private static double DEFAULT_NUCLEOTIDE_SPACING = 10.0;
        private static double DEFAULT_NUCLEOTIDES_PER_ROW = 20.0;

        private static readonly FontFamilyConverter fontFamilyChecker = new FontFamilyConverter();
        private static readonly FontWeightConverter fontWeightChecker = new FontWeightConverter();
        private static readonly FontStyleConverter fontStyleChecker = new FontStyleConverter();
        private static readonly ColorConverter colorChecker = new ColorConverter();

        private void FireStructureDrawingVisualPropertiesUpdate()
        {
            if (StructurePropertiesChangedEvent != null)
                StructurePropertiesChangedEvent(this, EventArgs.Empty);
        }

        private void FireDrawingBoundsChangedUpdate()
        {
            if (DrawingBoundsChangedEvent != null)
                DrawingBoundsChangedEvent(this, EventArgs.Empty);
        }

        private void FireDrawingDirtyStateChanged()
        {
            if (DirtyStateChangedEvent != null)
                DirtyStateChangedEvent(this, EventArgs.Empty);
        }

        private void SetSnapshotBrush()
        {
            ContainerVisual snapshotVisual = new ContainerVisual();
            foreach (var nt in NucleotideVisualsBySequenceIndex.Values)
                snapshotVisual.Children.Add(nt.MakeVisualSnapshot());
            foreach (var bp in BasePairVisualsByFivePrimeIndex.Values)
                snapshotVisual.Children.Add(bp.MakeVisualSnapshot());
            SnapshotBrush = new VisualBrush(snapshotVisual);
        }

        private void SetRangeSelectionGroup()
        {
            if (!ElementSelectionGroups.ContainsKey(StructureElementSelectionMode.Range))
                ElementSelectionGroups.Add(StructureElementSelectionMode.Range, new List<IStructureElementSelectionGroup>());

            if (FullModelRangeSelectionVisual == null)
            {
                FullModelRangeSelectionVisual = (BasePairVisualsByFivePrimeIndex.Count() <= 0) ? new RangeStructureElementVisualComponent(NucleotideVisualsBySequenceIndex) :
                    new RangeStructureElementVisualComponent(NucleotideVisualsBySequenceIndex, BasePairVisualsByFivePrimeIndex);
            }

            ElementSelectionGroups[StructureElementSelectionMode.Range].Clear();
            RangeSelectionGroup rangeSelectionGroup = new RangeSelectionGroup(this);    
            ElementSelectionGroups[StructureElementSelectionMode.Range].Add(rangeSelectionGroup);
        }

        private void SetDomainSelectionGroups()
        {
            if (!ElementSelectionGroups.ContainsKey(StructureElementSelectionMode.Domain))
                ElementSelectionGroups.Add(StructureElementSelectionMode.Domain, new List<IStructureElementSelectionGroup>());

            if (FullModelRangeSelectionVisual == null)
            {
                FullModelRangeSelectionVisual = (BasePairVisualsByFivePrimeIndex.Count() <= 0) ? new RangeStructureElementVisualComponent(NucleotideVisualsBySequenceIndex) :
                    new RangeStructureElementVisualComponent(NucleotideVisualsBySequenceIndex, BasePairVisualsByFivePrimeIndex);
            }

            ElementSelectionGroups[StructureElementSelectionMode.Domain].Clear();
            foreach (var helix in HelixVisualsByFivePrimeStartIndex.Values)
            {
                if (!helix.IsPseudoknotted)
                {
                    DomainSelectionGroup domainSelectionGroup = new DomainSelectionGroup(this, helix);
                    ElementSelectionGroups[StructureElementSelectionMode.Domain].Add(domainSelectionGroup);
                }
            }
            
        }

        private void ShiftRendering(Point delta)
        {
            DrawingBounds = Rect.Empty;
            //bool enforceBoundsEnabled = EnforceBounds;

            /*if (enforceBoundsEnabled) //We disable EnforceBounds to reposition the drawing.
                EnforceBounds = false;*/

            foreach (var loopID in LoopVisualsByStartIndex.Keys)
                MoveLoopBy(loopID, delta);

            foreach (var helixID in HelixVisualsByFivePrimeStartIndex.Keys)
                MoveHelixBy(helixID, delta);

            foreach (var ntID in NoParentExtentNucleotideVisualsBySequenceIndex.Keys)
                MoveNucleotideBy(ntID, delta, false, false, false);

            /*if (enforceBoundsEnabled) //We re-enable EnforceBounds if it was set
                EnforceBounds = true;*/

            if (!DrawingBounds.Equals(Rect.Empty))
                CanvasBounds = new Rect(0, 0, DrawingBounds.Width + 2 * CanvasPadding.X, DrawingBounds.Height + 2 * CanvasPadding.Y);
            else
                CanvasBounds = Rect.Empty;
            FireDrawingBoundsChangedUpdate();
        }

        private void CheckAndUpdateRenderingTopLeft(bool snapTopLeft)
        {
            if (snapTopLeft)
            {
                if (DrawingBounds != Rect.Empty && (((CanvasPadding.X > 0.0 && DrawingBounds.Left != CanvasPadding.X) || DrawingBounds.Left != 0.0) &&
                    ((CanvasPadding.Y > 0.0 && DrawingBounds.Top != CanvasPadding.Y) || DrawingBounds.Top != 0.0)))
                {
                    double shiftX = (CanvasPadding.X > 0.0) ? (CanvasPadding.X - DrawingBounds.Left) : (0.0 - DrawingBounds.Left);
                    double shiftY = (CanvasPadding.Y > 0.0) ? (CanvasPadding.Y - DrawingBounds.Top) : (0.0 - DrawingBounds.Top);
                    ShiftRendering(new Point(shiftX, shiftY));
                }
            }
            else
            {
                if (DrawingBounds != Rect.Empty && (((CanvasPadding.X > 0.0 && DrawingBounds.Left != CanvasPadding.X) || DrawingBounds.Left != 0.0) ||
                    ((CanvasPadding.Y > 0.0 && DrawingBounds.Top != CanvasPadding.Y) || DrawingBounds.Top != 0.0)))
                {
                    double shiftX = (CanvasPadding.X > 0.0) ? (CanvasPadding.X - DrawingBounds.Left) : (0.0 - DrawingBounds.Left);
                    double shiftY = (CanvasPadding.Y > 0.0) ? (CanvasPadding.Y - DrawingBounds.Top) : (0.0 - DrawingBounds.Top);
                    ShiftRendering(new Point(shiftX, shiftY));
                }
            }
        }

        //Tries to keep the minX and minY to 0.0, the drawing stays in the first quadrant, primairly necessary because XRNA dosen't use the upper left corner as (0,0) like Windows.
        //I suppose this could be handled in the provider, but then every supported format would have to do this management, which is partially dependent on the visualization (visual bounds calcs)
        //In my opinion, it makes more sense to just do this here.
        /*private void SnapRenderingToTopLeft()
        {
            if (DrawingBounds != Rect.Empty && DrawingBounds.Left != 0.0 && DrawingBounds.Top != 0.0)
            {
                double shiftX = (0.0 - DrawingBounds.Left);
                double shiftY = (0.0 - DrawingBounds.Top);
                ShiftRendering(new Point(shiftX, shiftY));
            }
        }*/

        private void CheckAndUpdateBounds(Rect test)
        {
            if (!DrawingBounds.Contains(test))
            {
                DrawingBounds = Rect.Union(DrawingBounds, test);
                FireDrawingBoundsChangedUpdate();
            }
        }

        private void DisconnectExtentsForModelEdits()
        {
            //Disconnect Any Extents and Unpaired Nucleotides from the Visual Tree
            foreach (var loopVisual in LoopVisualsByStartIndex.Values)
            {
                StructureDrawingVisual.RemoveChild(loopVisual);
                foreach (var nt in loopVisual.NucleotideIndices)
                    loopVisual.RemoveChild(NucleotideVisualsBySequenceIndex[nt]);
            }
            ElementSelectionGroups.Remove(StructureElementSelectionMode.Strand);

            foreach (var helixVisual in HelixVisualsByFivePrimeStartIndex.Values)
            {
                StructureDrawingVisual.RemoveChild(helixVisual);
                foreach (var bp in helixVisual.BasePairs)
                    helixVisual.RemoveChild(BasePairVisualsByFivePrimeIndex[bp.FivePrimeIndex]);
            }
            ElementSelectionGroups.Remove(StructureElementSelectionMode.Helix);

            foreach (var ntVisual in NoParentExtentNucleotideVisualsBySequenceIndex.Values)
                StructureDrawingVisual.RemoveChild(ntVisual);

            //Reset the Extent Visuals (this is where we might need to think about caching off edit state, etc...)
            NoParentExtentNucleotideVisualsBySequenceIndex.Clear();
            LoopVisualsByContainingNucleotideIndex.Clear();
            LoopVisualsByStartIndex.Clear();
            HelixVisualsByContainingNucleotideIndex.Clear();
            HelixVisualsByFivePrimeStartIndex.Clear();
            HelixVisualsByThreePrimeStartIndex.Clear();
        }

        private void ResetManager()
        {
            EndEditTracking();
            foreach (var loopVisual in LoopVisualsByStartIndex.Values)
            {
                StructureDrawingVisual.RemoveChild(loopVisual);
                foreach (var nt in loopVisual.NucleotideIndices)
                    loopVisual.RemoveChild(NucleotideVisualsBySequenceIndex[nt]);
            }

            foreach (var helixVisual in HelixVisualsByFivePrimeStartIndex.Values)
            {
                StructureDrawingVisual.RemoveChild(helixVisual);
                foreach (var bp in helixVisual.BasePairs)
                    helixVisual.RemoveChild(BasePairVisualsByFivePrimeIndex[bp.FivePrimeIndex]);
            }

            foreach (var bpVisual in BasePairVisualsByFivePrimeIndex.Values)
            {
                bpVisual.RemoveChild(NucleotideVisualsBySequenceIndex[bpVisual.Data.FivePrimeIndex]);
                bpVisual.RemoveChild(NucleotideVisualsBySequenceIndex[bpVisual.Data.ThreePrimeIndex]);
            }

            foreach (var ntVisual in NoParentExtentNucleotideVisualsBySequenceIndex.Values)
                StructureDrawingVisual.RemoveChild(ntVisual);

            BasePairVisualsByFivePrimeIndex.Clear();
            BasePairVisualsByThreePrimeIndex.Clear();
            NucleotideVisualsBySequenceIndex.Clear();
            NoParentExtentNucleotideVisualsBySequenceIndex.Clear();
            HelixVisualsByFivePrimeStartIndex.Clear();
            HelixVisualsByThreePrimeStartIndex.Clear();
            HelixVisualsByContainingNucleotideIndex.Clear();
            LoopVisualsByStartIndex.Clear();
            LoopVisualsByContainingNucleotideIndex.Clear();
            CurrentRenderingState = null;
            ElementSelectionGroups.Clear();
            EditStack.Clear();
            FullModelRangeSelectionVisual = null;
            SnapshotBrush = null;
            StructureModelProperties = null;
        }

        private void StartEditTracking()
        {
            foreach (var ntVisual in NucleotideVisualsBySequenceIndex)
            {
                ntVisual.Value.StartEditTracking();
                ntVisual.Value.NotifyNucleotideVisualEdited += new EventHandler(OnNotifyNucleotideVisualEditedEvent);
            }

            foreach (var bpVisual in BasePairVisualsByFivePrimeIndex)
            {
                bpVisual.Value.StartEditTracking();
                bpVisual.Value.NotifyBasePairVisualEdited += new EventHandler(OnNotifyBasePairVisualEditedEvent);
            }

            StructureDrawingViewModel.StartEditTracking();
            StructureDrawingViewModel.NotifyStructureDrawingVisualEdited += new EventHandler(OnNotifyStructureDrawingVisualEditedEvent);

            FullModelRangeSelectionVisual.NotifyStructureComponentVisualsEdited += new EventHandler<MixedRangeEditSetEventArgs>(OnNotifyStructureComponentVisualsEditedEvent);
        }

        private void EndEditTracking()
        {
            foreach (var ntVisual in NucleotideVisualsBySequenceIndex)
            {
                ntVisual.Value.NotifyNucleotideVisualEdited -= new EventHandler(OnNotifyNucleotideVisualEditedEvent);
            }

            foreach (var bpVisual in BasePairVisualsByFivePrimeIndex)
            {
                bpVisual.Value.NotifyBasePairVisualEdited -= new EventHandler(OnNotifyBasePairVisualEditedEvent);
            }

            if(FullModelRangeSelectionVisual != null) FullModelRangeSelectionVisual.NotifyStructureComponentVisualsEdited -= new EventHandler<MixedRangeEditSetEventArgs>(OnNotifyStructureComponentVisualsEditedEvent);
            if(StructureDrawingViewModel != null) StructureDrawingViewModel.NotifyStructureDrawingVisualEdited -= new EventHandler(OnNotifyStructureDrawingVisualEditedEvent);
        }

        private void CreateLoopVisuals()
        {
            Debug.Assert(StructureDrawingViewModel != null);
            Debug.Assert(StructureDrawingViewModel.CurrentStructure != null);
            Debug.Assert(StructureDrawingViewModel.CurrentStructure.Entity != null);
            Debug.Assert(StructureDrawingViewModel.CurrentStructure.Entity != null);
            Debug.Assert(StructureDrawingViewModel.CurrentStructure.Entity.Extents != null);
            var loops = StructureDrawingViewModel.CurrentStructure.Entity.Extents.Where(extent => (extent.Type == StructureExtentType.Bulge || extent.Type == StructureExtentType.Free ||
                extent.Type == StructureExtentType.Hairpin || extent.Type == StructureExtentType.Internal || extent.Type == StructureExtentType.Multistem ||
                extent.Type == StructureExtentType.Tail)); //Its not necessary to group because the loop extents currently only have a single ordinal.
            foreach (var loop in loops)
            {
                try
                {
                    if (loop.StartIndex > loop.EndIndex)
                        throw new ArgumentException(string.Format("Error: Strand with ID {0} has start index > end index, start index {1}, end index {2}", loop.ID, loop.StartIndex + 1, loop.EndIndex + 1));

                    if (loop.StartIndex < 0 || (loop.StartIndex > (StructureDrawingViewModel.CurrentStructure.Entity.Sequence.SequenceData.Count() - 1)))
                        throw new ArgumentException(string.Format("Error Strand with ID {0} has start index outside sequence bounds, start index {1}", loop.ID, loop.StartIndex + 1));

                    if (loop.EndIndex < 0 || (loop.EndIndex > (StructureDrawingViewModel.CurrentStructure.Entity.Sequence.SequenceData.Count() - 1)))
                        throw new ArgumentException(string.Format("Error: Strand with ID {0} has end index outside sequence bounds, end index {1}", loop.ID, loop.EndIndex + 1));

                    IEnumerable<int> loopindexes = Enumerable.Range(loop.StartIndex, (loop.EndIndex - loop.StartIndex + 1));
                    IEnumerable<int> loopindexesInHelices = HelixVisualsByContainingNucleotideIndex.Select(pair => pair.Key).Intersect(loopindexes);

                    if (loopindexesInHelices.Count() > 0)
                        throw new ArgumentException(string.Format("Error: String with ID {0} has elements ({1}) that are also in helices", loop.ID, loopindexesInHelices.ToList<int>().ToString())); 
                    
                    var strandSequence = from idx in loopindexes
                                         select StructureDrawingViewModel.CurrentStructure.Entity.Sequence.SequenceData[idx];

                    var loopNtVisuals = from ntIdx in loopindexes
                                        select NucleotideVisualsBySequenceIndex[ntIdx];

                    Dictionary<int, Point> allNtVisualLocations = new Dictionary<int, Point>();
                    foreach (var ntVisual in loopNtVisuals)
                        allNtVisualLocations.Add(ntVisual.NucleotideIndex, ntVisual.Location);

                    StrandArc loopArc = new StrandArc(loop.StartIndex, loop.EndIndex, allNtVisualLocations);
                    LoopStructureElementVisualComponent loopVisual = new LoopStructureElementVisualComponent(loopArc, loop.StartIndex, loop.EndIndex, strandSequence, loop.Type);
                    
                    foreach (var ntVisual in loopNtVisuals)
                    {
                        if (NoParentExtentNucleotideVisualsBySequenceIndex.ContainsKey(ntVisual.NucleotideIndex))
                        {
                            StructureDrawingVisual.RemoveChild(ntVisual);
                            NoParentExtentNucleotideVisualsBySequenceIndex.Remove(ntVisual.NucleotideIndex);
                        }
                        loopVisual.AddChild(ntVisual);
                    }

                    StructureDrawingVisual.AddChild(loopVisual);
                    loopVisual.PropagateVisualPropertyChanged();

                    LoopVisualsByStartIndex.Add(loop.StartIndex, loopVisual);

                    foreach (var nt in loopVisual.NucleotideIndices)
                        LoopVisualsByContainingNucleotideIndex.Add(nt, loopVisual);

                    LoopSelectionGroup loopSelector = new LoopSelectionGroup(this, loopVisual);
                    if (!ElementSelectionGroups.ContainsKey(StructureElementSelectionMode.Strand))
                        ElementSelectionGroups.Add(StructureElementSelectionMode.Strand, new List<IStructureElementSelectionGroup>());
                    ElementSelectionGroups[StructureElementSelectionMode.Strand].Add(loopSelector);
                }
                catch (Exception)
                {
                    continue; //We just won't pickup the loop.
                }
            }

            //Try to make other loops aware of their contextually related strands
            var multiStrandLoops = StructureDrawingViewModel.CurrentStructure.Entity.Extents.Where(extent => (extent.Type == StructureExtentType.Multistem || extent.Type == StructureExtentType.Internal)).GroupBy(extent => extent.ID);
            foreach (var multiStrandLoop in multiStrandLoops)
            {
                var loopStrands = multiStrandLoop.OrderBy(loopStrand => loopStrand.Ordinal).ToList();
                foreach (var loopStrand in loopStrands)
                {
                    var currentStrand = loopStrand;
                    foreach (var nestedLoopStrand in loopStrands)
                    {
                        if (nestedLoopStrand.Ordinal != currentStrand.Ordinal)
                            LoopVisualsByStartIndex[currentStrand.StartIndex].AddContextuallyRelatedStrand(LoopVisualsByStartIndex[nestedLoopStrand.StartIndex]);
                    }
                }
            }
        }

        private void CreateHelixVisuals()
        {
            Debug.Assert(StructureDrawingViewModel != null);
            Debug.Assert(StructureDrawingViewModel.CurrentStructure != null);
            Debug.Assert(StructureDrawingViewModel.CurrentStructure.Entity != null);
            Debug.Assert(StructureDrawingViewModel.CurrentStructure.Entity.BasePairs != null);
            Debug.Assert(StructureDrawingViewModel.CurrentStructure.Entity.Extents != null);
            var helices = StructureDrawingViewModel.CurrentStructure.Entity.Extents.Where(extent => (extent.Type == StructureExtentType.Helix || extent.Type == StructureExtentType.Knot)).GroupBy(extent => extent.ID);
            foreach (var helix in helices)
            {
                var helixStrands = helix.OrderBy(helixStrand => helixStrand.Ordinal).ToList();
                if (helixStrands.Count == 2)
                {
                    bool isPseudoknotted = (helixStrands[0].Type == StructureExtentType.Helix) ? false : true;

                    int strand1length = (helixStrands[0].EndIndex - helixStrands[0].StartIndex) + 1;
                    int strand2length = (helixStrands[1].EndIndex - helixStrands[1].StartIndex) + 1;

                    if (strand1length != strand2length)
                        throw new ArgumentException(string.Format("Error: Helix with extent id {0}, strands are not equivalent length, 5' strand: {1}, 3' strand: {2}", helix.Key, strand1length, strand2length));

                    var basePairEntities = from pos in Enumerable.Range(helixStrands[0].StartIndex, strand1length)
                                           select BasePairVisualsByFivePrimeIndex[pos].Data;
                    try
                    {
                        //This loop will throw an exception if any of the base pairs can't be matched up in the base pair visual dictionary.
                        var basePairEntitiesFromThreePrimeStrand = from pos in Enumerable.Range(helixStrands[1].StartIndex, strand2length)
                                                                   select BasePairVisualsByThreePrimeIndex[pos].Data;

                        if (basePairEntities.Count() != basePairEntitiesFromThreePrimeStrand.Count())
                            throw new ArgumentException(string.Format("Error: Helix with extent id {0}, inconstent base pairs identified from strands", helix.Key));

                        var basePairsStrandsCrossVerified = from bp5prime in basePairEntities.OrderBy(bp => bp.FivePrimeIndex)
                                                            from bp3prime in basePairEntitiesFromThreePrimeStrand.OrderBy(bp => bp.FivePrimeIndex)
                                                            where bp5prime.FivePrimeIndex == bp3prime.FivePrimeIndex &&
                                                            bp5prime.ThreePrimeIndex == bp3prime.ThreePrimeIndex
                                                            select bp5prime;
                        if (basePairsStrandsCrossVerified.Count() == basePairEntities.Count())
                        {
                            var helixNtVisuals = (from pos5p in Enumerable.Range(helixStrands[0].StartIndex, strand1length)
                                                  select NucleotideVisualsBySequenceIndex[pos5p]).Union(from pos3p in Enumerable.Range(helixStrands[1].StartIndex, strand2length)
                                                                                                        select NucleotideVisualsBySequenceIndex[pos3p]);
                            Dictionary<int, Point> allNtVisualLocations = new Dictionary<int, Point>();
                            foreach (var ntVisual in helixNtVisuals)
                                allNtVisualLocations.Add(ntVisual.NucleotideIndex, ntVisual.Location);

                            HelixRectangle helixShape = new HelixRectangle(helixStrands[0].StartIndex, helixStrands[0].EndIndex,
                                        helixStrands[1].StartIndex, helixStrands[1].EndIndex, allNtVisualLocations);

                            HelixStructureElementVisualComponent helixVisual = new HelixStructureElementVisualComponent(helixShape, helixStrands[0].StartIndex, helixStrands[0].EndIndex,
                                        helixStrands[1].StartIndex, helixStrands[1].EndIndex, basePairsStrandsCrossVerified, helixStrands[0].Elements, helixStrands[1].Elements, isPseudoknotted);

                            foreach (var nt in helixVisual.NucleotideIndices)
                                HelixVisualsByContainingNucleotideIndex.Add(nt, helixVisual);

                            var basePairVisuals = from pos in Enumerable.Range(helixStrands[0].StartIndex, strand1length)
                                                  select BasePairVisualsByFivePrimeIndex[pos];
                            foreach (var bpVisual in basePairVisuals)
                                helixVisual.AddChild(bpVisual);

                            StructureDrawingVisual.AddChild(helixVisual);
                            helixVisual.PropagateVisualPropertyChanged();

                            HelixVisualsByFivePrimeStartIndex.Add(helixVisual.FivePrimeStartNucleotideIndex, helixVisual);
                            HelixVisualsByThreePrimeStartIndex.Add(helixVisual.ThreePrimeStartNucleotideIndex, helixVisual);

                            HelixSelectionGroup helixSelector = new HelixSelectionGroup(this, helixVisual);
                            if (!ElementSelectionGroups.ContainsKey(StructureElementSelectionMode.Helix))
                                ElementSelectionGroups.Add(StructureElementSelectionMode.Helix, new List<IStructureElementSelectionGroup>());
                            ElementSelectionGroups[StructureElementSelectionMode.Helix].Add(helixSelector);
                        }
                        else
                            throw new ArgumentException(string.Format("Error: Helix with extent id {0}, inconstent base pairs identified from strands", helix.Key));
                    }
                    catch(Exception e) 
                    {
                        throw new ArgumentException(string.Format("Error: Helix with extent id {0} specifies {1} base pairs, but not all those pairs exist in the structure model", helix.Key, strand1length), e);
                    }
                }
                else
                    throw new ArgumentException(string.Format("Error: Helix with extent id: {0} does not have 2 strands", helix.Key));
            }
        }

        private void CreateBasePairVisuals()
        {
            Debug.Assert(StructureDrawingViewModel != null);
            Debug.Assert(StructureDrawingViewModel.CurrentStructure != null);
            Debug.Assert(StructureDrawingViewModel.CurrentStructure.Entity != null);
            Debug.Assert(StructureDrawingViewModel.CurrentStructure.Entity.BasePairs != null);
            CreateBasePairVisuals(StructureDrawingViewModel.CurrentStructure.Entity.BasePairs);
        }

        private void CreateBasePairVisuals(IEnumerable<IBasePairBioEntity> newBasePairs)
        {
            Debug.Assert(newBasePairs != null);
            foreach (var basePair in newBasePairs)
            {
                Debug.Assert(NucleotideVisualsBySequenceIndex.ContainsKey(basePair.FivePrimeIndex));
                Debug.Assert(NucleotideVisualsBySequenceIndex.ContainsKey(basePair.ThreePrimeIndex));
                BasePairStructureElementVisualComponent bpVisual = BasePairStructureElementVisualComponentFactory.Create(basePair);
                BasePairVisualsByFivePrimeIndex.Add(bpVisual.Data.FivePrimeIndex, bpVisual);
                BasePairVisualsByThreePrimeIndex.Add(bpVisual.Data.ThreePrimeIndex, bpVisual);
                //pairedNt.Add(bpVisual.Data.FivePrimeIndex);
                //pairedNt.Add(bpVisual.Data.ThreePrimeIndex);
                bpVisual.StartingBasePairPropertyResolve += new EventHandler(OnStartingBasePairVisualPropertyResolveEvent);
                bpVisual.EndingBasePairPropertyResolve += new EventHandler(OnEndingBasePairVisualPropertyResolveEvent);

                if (UsingDefaultRendering)
                {
                    bpVisual.BasePairConnectorIsVisible = false;
                    bpVisual.NucleotideFontWeight = FontWeights.Bold;
                    bpVisual.NucleotideFill = new SolidColorBrush(Colors.Red);
                }
                
                //KJD: Commented out becuase base pairs are included in the hierarchy through helices.
                //StructureDrawingVisual.AddChild(bpVisual);
                bpVisual.AddChild(NucleotideVisualsBySequenceIndex[bpVisual.Data.FivePrimeIndex]);
                bpVisual.AddChild(NucleotideVisualsBySequenceIndex[bpVisual.Data.ThreePrimeIndex]);

                BasePairSelectionGroup bpSelector = new BasePairSelectionGroup(this, bpVisual);
                if (!ElementSelectionGroups.ContainsKey(StructureElementSelectionMode.BasePair))
                    ElementSelectionGroups.Add(StructureElementSelectionMode.BasePair, new List<IStructureElementSelectionGroup>());
                ElementSelectionGroups[StructureElementSelectionMode.BasePair].Add(bpSelector);
            }

            List<int> pairedNt = new List<int>();
            foreach (var bp in BasePairVisualsByFivePrimeIndex)
            {
                pairedNt.Add(bp.Value.Data.FivePrimeIndex);
                pairedNt.Add(bp.Value.Data.ThreePrimeIndex);
            }
            var unPairedNt = NucleotideVisualsBySequenceIndex.Keys.Except(pairedNt);
            foreach (var unPairedNtIdx in unPairedNt)
            {
                NoParentExtentNucleotideVisualsBySequenceIndex.Add(unPairedNtIdx, NucleotideVisualsBySequenceIndex[unPairedNtIdx]);
                StructureDrawingVisual.AddChild(NucleotideVisualsBySequenceIndex[unPairedNtIdx]);
                NucleotideVisualsBySequenceIndex[unPairedNtIdx].PropagateVisualPropertyChanged();
            }
        }

        private void CreateNucleotideVisuals()
        {
            Debug.Assert(StructureDrawingViewModel != null);
            Debug.Assert(StructureDrawingViewModel.CurrentStructure != null);
            Debug.Assert(StructureDrawingViewModel.CurrentStructure.Entity != null);
            Debug.Assert(StructureDrawingViewModel.CurrentStructure.Entity.Sequence != null);
            for (int i = 0; i < StructureDrawingViewModel.CurrentStructure.Entity.Sequence.SequenceData.Count(); i++)
            {
                string ntVal = Char.ToString(StructureDrawingViewModel.CurrentStructure.Entity.Sequence.SequenceData[i].Value);
                int totalNt = StructureDrawingViewModel.CurrentStructure.Entity.Sequence.SequenceData.Count();
                Point currentNt = new Point(CurrentRenderingState.RenderingMap[i].StartX, CurrentRenderingState.RenderingMap[i].StartY);
                Point nextNt = new Point();
                if ((i + 1) < totalNt)
                    nextNt = new Point(CurrentRenderingState.RenderingMap[i + 1].StartX, CurrentRenderingState.RenderingMap[i + 1].StartY);
                else
                    nextNt = new Point(CurrentRenderingState.RenderingMap[0].StartX, CurrentRenderingState.RenderingMap[0].StartY);
                Point prevNt = new Point();
                if((i - 1) >= 0) 
                    prevNt = new Point(CurrentRenderingState.RenderingMap[i - 1].StartX, CurrentRenderingState.RenderingMap[i - 1].StartY);
                else
                    prevNt = new Point(CurrentRenderingState.RenderingMap[totalNt - 1].StartX, CurrentRenderingState.RenderingMap[totalNt - 1].StartY);    
                //NucleotideVector ntVector = new NucleotideVector(currentNt, nextNt, prevNt);
                NucleotideVector ntVector = new NucleotideVector(new Point(0,0), nextNt, prevNt);
                /*NucleotideVector ntVector = (i + 1 <= StructureDrawingViewModel.CurrentStructure.Entity.Sequence.SequenceData.Count() - 1) ? new NucleotideVector(new Point(CurrentRenderingState.RenderingMap[i].StartX, CurrentRenderingState.RenderingMap[i].StartY),
                    new Point(CurrentRenderingState.RenderingMap[i + 1].StartX, CurrentRenderingState.RenderingMap[i + 1].StartY)) : new NucleotideVector(new Point(CurrentRenderingState.RenderingMap[i].StartX, CurrentRenderingState.RenderingMap[i].StartY),
                        new Point(CurrentRenderingState.RenderingMap[i].StartX, CurrentRenderingState.RenderingMap[i].StartY));*/
                NucleotideStructureElementVisualComponent ntVisual = NucleotideStructureElementVisualComponentFactory.Create(ntVector, i, StructureDrawingViewModel.CurrentStructure.Entity.Sequence.SequenceData[i]);
                NucleotideVisualsBySequenceIndex.Add(i, ntVisual);
                ntVisual.StartingNucleotidePropertyResolve += new EventHandler(OnStartingNucleotideVisualPropertyResolveEvent);
                ntVisual.EndingNucleotidePropertyResolve += new EventHandler(OnEndingNucleotideVisualPropertyResolveEvent);

                //KJD: Commented out because nucleotides are added into the hierarchy through loops or helices.
                //StructureDrawingVisual.AddChild(ntVisual);
                //ntVisual.PropagateVisualPropertyChanged();
                
                NucleotideSelectionGroup ntSelector = new NucleotideSelectionGroup(this, ntVisual);
                if (!ElementSelectionGroups.ContainsKey(StructureElementSelectionMode.Nucleotide))
                    ElementSelectionGroups.Add(StructureElementSelectionMode.Nucleotide, new List<IStructureElementSelectionGroup>());
                ElementSelectionGroups[StructureElementSelectionMode.Nucleotide].Add(ntSelector);
            }
        }

        private void SetInitialSequenceRendering()
        {
            Debug.Assert(StructureDrawingViewModel != null);
            Debug.Assert(StructureDrawingViewModel.CurrentStructure != null);
            Debug.Assert(StructureDrawingViewModel.CurrentStructure.Entity != null);
            Debug.Assert(StructureDrawingViewModel.CurrentStructure.Entity.Sequence != null);
            CurrentRenderingState = new SequenceRendering();

            //Lets look to see if the sequence has a rendering, if it does, we take it.
            IExtensibleProperties metadata = StructureDrawingViewModel.CurrentStructure.Entity.Sequence as IExtensibleProperties;
            if (metadata != null && metadata.DoesExtendedPropertyExist(SequenceMetadata.Key))
            {
                SequenceRendering rendering = null;
                SequenceMetadata seqMetadata = metadata.GetExtendedProperty<SequenceMetadata>(SequenceMetadata.Key);
                if (seqMetadata.Data.ContainsKey(SequenceRendering.Key))
                {
                    rendering = (SequenceRendering)seqMetadata.Data[SequenceRendering.Key];
                }

                foreach (int ntIdx in rendering.RenderingMap.Keys)
                {
                    IVector positionCopy = rendering.RenderingMap[ntIdx].Clone();
                    CurrentRenderingState.RenderingMap.Add(ntIdx, positionCopy);
                }
                return;
            }
            else
            {
                UsingDefaultRendering = true;
                double rowOffset = DEFAULT_NUCLEOTIDE_SPACING;
                double rows = StructureDrawingViewModel.CurrentStructure.Entity.Sequence.SequenceData.Count / DEFAULT_NUCLEOTIDES_PER_ROW;
                if (StructureDrawingViewModel.CurrentStructure.Entity.Sequence.SequenceData.Count % DEFAULT_NUCLEOTIDES_PER_ROW != 0)
                    rows++; //Add and extra row for leftovers.

                double currentXPos = DEFAULT_NUCLEOTIDE_SPACING;
                double currentYPos = DEFAULT_NUCLEOTIDE_SPACING;
                for (int i = 0; i < StructureDrawingViewModel.CurrentStructure.Entity.Sequence.SequenceData.Count; i++)
                {
                    if (i > 0 && i % 50 == 0) //We reposition to the next row.
                    {
                        currentXPos = DEFAULT_NUCLEOTIDE_SPACING;
                        currentYPos += DEFAULT_NUCLEOTIDE_SPACING;
                    }

                    IVector ntPosition = new LocalNucleotideSequenceVector()
                    {
                        StartX = currentXPos,
                        StartY = currentYPos,
                        RayAngle = 0.0,
                        Magnitude = DEFAULT_NUCLEOTIDE_SPACING
                    };
                    CurrentRenderingState.RenderingMap.Add(i, ntPosition);
                    currentXPos += DEFAULT_NUCLEOTIDE_SPACING;
                }

            }
        }

        private void OnNotifyStructureDrawingVisualEditedEvent(object sender, EventArgs args)
        {
            SecondaryStructureEditorViewModel drawingViewModel = sender as SecondaryStructureEditorViewModel;
            if (drawingViewModel != null)
            {
                EditStack.Push(new StructureModelEdit()
                {
                    VisualsWithPropertyEdits = new List<IStructureElementVisualComponent>() { drawingViewModel },
                    EditCategory = StructureModelEdit.EditType.Properties
                });
                FireDrawingDirtyStateChanged();
            }
        }

        private void OnNotifyBasePairVisualEditedEvent(object sender, EventArgs args)
        {
            BasePairStructureElementVisualComponent basePairEdited = sender as BasePairStructureElementVisualComponent;
            if (basePairEdited != null)
            {
                EditStack.Push(new StructureModelEdit()
                {
                    VisualsWithPropertyEdits = new List<IStructureElementVisualComponent>() { basePairEdited },
                    EditCategory = StructureModelEdit.EditType.Properties
                });
                FireDrawingDirtyStateChanged();
            }
        }

        private void OnNotifyStructureComponentVisualsEditedEvent(object sender, MixedRangeEditSetEventArgs args)
        {
            List<IStructureElementVisualComponent> edits = new List<IStructureElementVisualComponent>();

            if (args.ModifiedNucleotides != null && args.ModifiedNucleotides.Count > 0)
            {
                foreach (var modifiedNucleotide in args.ModifiedNucleotides)
                    edits.Add(modifiedNucleotide);
            }

            if (args.ModifiedBasePairs != null && args.ModifiedBasePairs.Count > 0)
            {
                foreach (var modifiedBasePair in args.ModifiedBasePairs)
                    edits.Add(modifiedBasePair);
            }

            if (edits.Count > 0)
            {
                EditStack.Push(new StructureModelEdit()
                {
                    VisualsWithPropertyEdits = edits,
                    EditCategory = StructureModelEdit.EditType.Properties
                });
                FireDrawingDirtyStateChanged();
            }   
        }

        private void OnNotifyNucleotideVisualEditedEvent(object sender, EventArgs args)
        {
            NucleotideStructureElementVisualComponent nucleotideEdited = sender as NucleotideStructureElementVisualComponent;
            if (nucleotideEdited != null)
            {
                EditStack.Push(new StructureModelEdit()
                {
                    VisualsWithPropertyEdits = new List<IStructureElementVisualComponent>() { nucleotideEdited },
                    EditCategory = StructureModelEdit.EditType.Properties
                });
                FireDrawingDirtyStateChanged();
            }
        }

        private void OnStartingNucleotideVisualPropertyResolveEvent(object sender, EventArgs args)
        {
            NucleotideStructureElementVisualComponent componentStartingResolve = sender as NucleotideStructureElementVisualComponent;
            if (componentStartingResolve != null && DataDrivenDefaultProviders != null && DataDrivenDefaultProviders.Count() > 0)
            {
                foreach (var provider in DataDrivenDefaultProviders)
                    provider.NotifyStartingNucleotidePropertyResolution(componentStartingResolve.Data);
            }
        }

        private void OnEndingNucleotideVisualPropertyResolveEvent(object sender, EventArgs args)
        {
            NucleotideStructureElementVisualComponent componentEndingResolve = sender as NucleotideStructureElementVisualComponent;
            if (componentEndingResolve != null && DataDrivenDefaultProviders != null && DataDrivenDefaultProviders.Count() > 0)
            {
                foreach (var provider in DataDrivenDefaultProviders)
                    provider.NotifyEndingNucleotidePropertyResolution();
            }
        }

        private void OnStartingBasePairVisualPropertyResolveEvent(object sender, EventArgs args)
        {
            BasePairStructureElementVisualComponent componentStartingResolve = sender as BasePairStructureElementVisualComponent;
            if (componentStartingResolve != null && DataDrivenDefaultProviders != null && DataDrivenDefaultProviders.Count() > 0)
            {
                foreach (var provider in DataDrivenDefaultProviders)
                    provider.NotifyStartingBasePairPropertyResolution(componentStartingResolve.Data);
            }
        }

        private void OnEndingBasePairVisualPropertyResolveEvent(object sender, EventArgs args)
        {
            BasePairStructureElementVisualComponent componentEndingResolve = sender as BasePairStructureElementVisualComponent;
            if (componentEndingResolve != null && DataDrivenDefaultProviders != null && DataDrivenDefaultProviders.Count() > 0)
            {
                foreach (var provider in DataDrivenDefaultProviders)
                    provider.NotifyEndingBasePairPropertyResolution();
            }
        }

        #endregion

        private class StructureModelEdit
        {
            public enum EditType { Model, Properties, Drawing };
            public List<IStructureElementVisualComponent> VisualsWithPropertyEdits { get; set; }
            public EditType EditCategory { get; set; }
            public List<DrawingEdit> VisualDrawingEdits { get; set; }
        }

        private class DrawingEdit
        {
            public Point MoveDelta { get; set; }
            public List<NucleotideStructureElementVisualComponent> NucleotideVisualsRepositionedByDelta { get; set; }
        }
    }
}
